diff options
author | caryclark <caryclark@google.com> | 2014-06-17 05:15:38 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-06-17 05:15:38 -0700 |
commit | dac1d17027dcaa5596885a9f333979418b35001c (patch) | |
tree | 923c6ca762654144254565240de5e9ec6598c41f | |
parent | d6043b20b63f895d384b4794205ac914abfafa71 (diff) |
Enabling the canvas bit to turn the clip stack into a flat replace exposed around 100 failures when testing the 800K skp set generated from the top 1M web sites.
This fixes all but one of those failures.
Major changes include:
- Replace angle indices with angle pointers. This was motivated by the need to add angles later but not renumber existing angles.
- Aggressive segment chase. When the winding is known on a segment, more aggressively passing that winding to adjacent segments allows fragmented data sets to succeed.
- Line segments with ends nearly the same are treated as coincident first.
- Transfer partial coincidence by observing that if segment A is partially coincident to B and C then B and C may be partially coincident.
TBR=reed
Author: caryclark@google.com
Review URL: https://codereview.chromium.org/272153002
39 files changed, 7093 insertions, 809 deletions
diff --git a/src/pathops/SkAddIntersections.cpp b/src/pathops/SkAddIntersections.cpp index 620842bf8c..52e751bd08 100644 --- a/src/pathops/SkAddIntersections.cpp +++ b/src/pathops/SkAddIntersections.cpp @@ -397,6 +397,7 @@ bool AddIntersectTs(SkOpContour* test, SkOpContour* next) { SkASSERT(ts[0][pt] >= 0 && ts[0][pt] <= 1); SkASSERT(ts[1][pt] >= 0 && ts[1][pt] <= 1); SkPoint point = ts.pt(pt).asSkPoint(); + wt.alignTPt(wn, swap, pt, &ts, &point); int testTAt = wt.addT(wn, point, ts[swap][pt]); int nextTAt = wn.addT(wt, point, ts[!swap][pt]); wt.addOtherT(testTAt, ts[!swap][pt], nextTAt); @@ -437,6 +438,10 @@ void CoincidenceCheck(SkTArray<SkOpContour*, true>* contourList, int total) { int contourCount = (*contourList).count(); for (int cIndex = 0; cIndex < contourCount; ++cIndex) { SkOpContour* contour = (*contourList)[cIndex]; + contour->resolveNearCoincidence(); + } + for (int cIndex = 0; cIndex < contourCount; ++cIndex) { + SkOpContour* contour = (*contourList)[cIndex]; contour->addCoincidentPoints(); } for (int cIndex = 0; cIndex < contourCount; ++cIndex) { diff --git a/src/pathops/SkDCubicIntersection.cpp b/src/pathops/SkDCubicIntersection.cpp index dd51195d77..1c237d9cf1 100644 --- a/src/pathops/SkDCubicIntersection.cpp +++ b/src/pathops/SkDCubicIntersection.cpp @@ -303,15 +303,39 @@ bool SkIntersections::cubicExactEnd(const SkDCubic& cubic1, bool start, const Sk continue; } if (swap) { - insert(testT, impTs[0][index], tmpLine[0]); + cubicInsert(testT, impTs[0][index], tmpLine[0], cubic2, cubic1); } else { - insert(impTs[0][index], testT, tmpLine[0]); + cubicInsert(impTs[0][index], testT, tmpLine[0], cubic1, cubic2); } return true; } return false; } + +void SkIntersections::cubicInsert(double one, double two, const SkDPoint& pt, + const SkDCubic& cubic1, const SkDCubic& cubic2) { + for (int index = 0; index < fUsed; ++index) { + if (fT[0][index] == one) { + double oldTwo = fT[1][index]; + if (oldTwo == two) { + return; + } + SkDPoint mid = cubic2.ptAtT((oldTwo + two) / 2); + if (mid.approximatelyEqual(fPt[index])) { + return; + } + } + if (fT[1][index] == two) { + SkDPoint mid = cubic1.ptAtT((fT[0][index] + two) / 2); + if (mid.approximatelyEqual(fPt[index])) { + return; + } + } + } + insert(one, two, pt); +} + void SkIntersections::cubicNearEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2, const SkDRect& bounds2) { SkDLine line; @@ -371,11 +395,15 @@ void SkIntersections::cubicNearEnd(const SkDCubic& cubic1, bool start, const SkD double tMin2 = SkTMax(tVals[tIdx] - LINE_FRACTION, 0.0); double tMax2 = SkTMin(tVals[tLast] + LINE_FRACTION, 1.0); int lastUsed = used(); - ::intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, *this); + if (start ? tMax1 < tMin2 : tMax2 < tMin1) { + ::intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, *this); + } if (lastUsed == used()) { tMin2 = SkTMax(tVals[tIdx] - (1.0 / SkDCubic::gPrecisionUnit), 0.0); tMax2 = SkTMin(tVals[tLast] + (1.0 / SkDCubic::gPrecisionUnit), 1.0); - ::intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, *this); + if (start ? tMax1 < tMin2 : tMax2 < tMin1) { + ::intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, *this); + } } tIdx = tLast + 1; } while (tIdx < tVals.count()); @@ -421,6 +449,9 @@ static bool only_end_pts_in_common(const SkDCubic& c1, const SkDCubic& c2) { } double adj = endPt[oppTest]->fX - origX; double opp = endPt[oppTest]->fY - origY; + if (adj == 0 && opp == 0) { // if the other point equals the test point, ignore it + continue; + } double sign = (c1[oddMan].fY - origY) * adj - (c1[oddMan].fX - origX) * opp; if (approximately_zero(sign)) { goto tryNextHalfPlane; @@ -531,7 +562,7 @@ int SkIntersections::intersect(const SkDCubic& c1, const SkDCubic& c2) { if (compCount) { int exactCount = used(); if (exactCount == 0) { - set(i); + *this = i; } else { // at least one is exact or near, and at least one was computed. Eliminate duplicates for (int exIdx = 0; exIdx < exactCount; ++exIdx) { diff --git a/src/pathops/SkDCubicLineIntersection.cpp b/src/pathops/SkDCubicLineIntersection.cpp index 41828bb714..696c42e835 100644 --- a/src/pathops/SkDCubicLineIntersection.cpp +++ b/src/pathops/SkDCubicLineIntersection.cpp @@ -261,7 +261,7 @@ public: if (fIntersections->hasT(cubicT)) { continue; } - double lineT = fLine.nearPoint(fCubic[cIndex]); + double lineT = fLine.nearPoint(fCubic[cIndex], NULL); if (lineT < 0) { continue; } diff --git a/src/pathops/SkDLineIntersection.cpp b/src/pathops/SkDLineIntersection.cpp index 89695395e6..9ae0107173 100644 --- a/src/pathops/SkDLineIntersection.cpp +++ b/src/pathops/SkDLineIntersection.cpp @@ -154,15 +154,52 @@ int SkIntersections::intersect(const SkDLine& a, const SkDLine& b) { computePoints(a, 1); } } +/* Allow tracking that both sets of end points are near each other -- the lines are entirely + coincident -- even when the end points are not exactly the same. + Mark this as a 'wild card' for the end points, so that either point is considered totally + coincident. Then, avoid folding the lines over each other, but allow either end to mate + to the next set of lines. + */ if (fAllowNear || !unparallel) { - for (int iA = 0; iA < 2; ++iA) { - if ((t = b.nearPoint(a[iA])) >= 0) { - insert(iA, t, a[iA]); - } + double aNearB[2]; + double bNearA[2]; + bool aNotB[2] = {false, false}; + bool bNotA[2] = {false, false}; + int nearCount = 0; + for (int index = 0; index < 2; ++index) { + aNearB[index] = t = b.nearPoint(a[index], &aNotB[index]); + nearCount += t >= 0; + bNearA[index] = t = a.nearPoint(b[index], &bNotA[index]); + nearCount += t >= 0; } - for (int iB = 0; iB < 2; ++iB) { - if ((t = a.nearPoint(b[iB])) >= 0) { - insert(t, iB, b[iB]); + if (nearCount > 0) { + for (int iA = 0; iA < 2; ++iA) { + if (!aNotB[iA]) { + continue; + } + int nearer = aNearB[iA] > 0.5; + if (!bNotA[nearer]) { + continue; + } + SkASSERT(a[iA] != b[nearer]); + SkASSERT(iA == (bNearA[nearer] > 0.5)); + fNearlySame[iA] = true; + insertNear(iA, nearer, a[iA], b[nearer]); + aNearB[iA] = -1; + bNearA[nearer] = -1; + nearCount -= 2; + } + if (nearCount > 0) { + for (int iA = 0; iA < 2; ++iA) { + if (aNearB[iA] >= 0) { + insert(iA, aNearB[iA], a[iA]); + } + } + for (int iB = 0; iB < 2; ++iB) { + if (bNearA[iB] >= 0) { + insert(bNearA[iB], iB, b[iB]); + } + } } } } @@ -240,12 +277,12 @@ int SkIntersections::horizontal(const SkDLine& line, double left, double right, } } if (fAllowNear || result == 2) { - if ((t = line.nearPoint(leftPt)) >= 0) { + if ((t = line.nearPoint(leftPt, NULL)) >= 0) { insert(t, (double) flipped, leftPt); } if (left != right) { const SkDPoint rightPt = { right, y }; - if ((t = line.nearPoint(rightPt)) >= 0) { + if ((t = line.nearPoint(rightPt, NULL)) >= 0) { insert(t, (double) !flipped, rightPt); } for (int index = 0; index < 2; ++index) { @@ -328,12 +365,12 @@ int SkIntersections::vertical(const SkDLine& line, double top, double bottom, } } if (fAllowNear || result == 2) { - if ((t = line.nearPoint(topPt)) >= 0) { + if ((t = line.nearPoint(topPt, NULL)) >= 0) { insert(t, (double) flipped, topPt); } if (top != bottom) { SkDPoint bottomPt = { x, bottom }; - if ((t = line.nearPoint(bottomPt)) >= 0) { + if ((t = line.nearPoint(bottomPt, NULL)) >= 0) { insert(t, (double) !flipped, bottomPt); } for (int index = 0; index < 2; ++index) { diff --git a/src/pathops/SkDQuadIntersection.cpp b/src/pathops/SkDQuadIntersection.cpp index 14ccac6186..5a8bafcdc5 100644 --- a/src/pathops/SkDQuadIntersection.cpp +++ b/src/pathops/SkDQuadIntersection.cpp @@ -422,7 +422,7 @@ int SkIntersections::intersect(const SkDQuad& q1, const SkDQuad& q2) { swapped.setMax(fMax); if (is_linear(q2, q1, &swapped)) { swapped.swapPts(); - set(swapped); + *this = swapped; return fUsed; } SkIntersections copyI(*this); diff --git a/src/pathops/SkDQuadLineIntersection.cpp b/src/pathops/SkDQuadLineIntersection.cpp index 1b9d8ccd38..ef8edb02cd 100644 --- a/src/pathops/SkDQuadLineIntersection.cpp +++ b/src/pathops/SkDQuadLineIntersection.cpp @@ -238,7 +238,7 @@ protected: if (fIntersections->hasT(quadT)) { continue; } - double lineT = fLine.nearPoint(fQuad[qIndex]); + double lineT = fLine.nearPoint(fQuad[qIndex], NULL); if (lineT < 0) { continue; } @@ -324,10 +324,10 @@ protected: *pt = fQuad.ptAtT(qT); } SkPoint gridPt = pt->asSkPoint(); - if (gridPt == fLine[0].asSkPoint()) { + if (SkDPoint::ApproximatelyEqual(gridPt, fLine[0].asSkPoint())) { *pt = fLine[0]; *lineT = 0; - } else if (gridPt == fLine[1].asSkPoint()) { + } else if (SkDPoint::ApproximatelyEqual(gridPt, fLine[1].asSkPoint())) { *pt = fLine[1]; *lineT = 1; } diff --git a/src/pathops/SkIntersectionHelper.h b/src/pathops/SkIntersectionHelper.h index 4e8c658ec2..3569c934de 100644 --- a/src/pathops/SkIntersectionHelper.h +++ b/src/pathops/SkIntersectionHelper.h @@ -54,6 +54,11 @@ public: return ++fIndex < fLast; } + void alignTPt(SkIntersectionHelper& other, bool swap, int index, + SkIntersections* ts, SkPoint* point) { + fContour->alignTPt(fIndex, other.fContour, other.fIndex, swap, index, ts, point); + } + SkScalar bottom() const { return bounds().fBottom; } diff --git a/src/pathops/SkIntersections.cpp b/src/pathops/SkIntersections.cpp index c8b4b838e4..56eba2717b 100644 --- a/src/pathops/SkIntersections.cpp +++ b/src/pathops/SkIntersections.cpp @@ -103,6 +103,7 @@ int SkIntersections::insert(double one, double two, const SkDPoint& pt) { int remaining = fUsed - index; if (remaining > 0) { memmove(&fPt[index + 1], &fPt[index], sizeof(fPt[0]) * remaining); + memmove(&fPt2[index + 1], &fPt2[index], sizeof(fPt2[0]) * remaining); memmove(&fT[0][index + 1], &fT[0][index], sizeof(fT[0][0]) * remaining); memmove(&fT[1][index + 1], &fT[1][index], sizeof(fT[1][0]) * remaining); int clearMask = ~((1 << index) - 1); @@ -116,6 +117,15 @@ int SkIntersections::insert(double one, double two, const SkDPoint& pt) { return index; } +void SkIntersections::insertNear(double one, double two, const SkDPoint& pt1, const SkDPoint& pt2) { + SkASSERT(one == 0 || one == 1); + SkASSERT(two == 0 || two == 1); + SkASSERT(pt1 != pt2); + SkASSERT(fNearlySame[(int) one]); + (void) insert(one, two, pt1); + fPt2[one ? fUsed - 1 : 0] = pt2; +} + void SkIntersections::insertCoincident(double one, double two, const SkDPoint& pt) { int index = insertSwap(one, two, pt); int bit = 1 << index; @@ -158,6 +168,7 @@ void SkIntersections::removeOne(int index) { return; } memmove(&fPt[index], &fPt[index + 1], sizeof(fPt[0]) * remaining); + memmove(&fPt2[index], &fPt2[index + 1], sizeof(fPt2[0]) * remaining); memmove(&fT[0][index], &fT[0][index + 1], sizeof(fT[0][0]) * remaining); memmove(&fT[1][index], &fT[1][index + 1], sizeof(fT[1][0]) * remaining); SkASSERT(fIsCoincident[0] == 0); diff --git a/src/pathops/SkIntersections.h b/src/pathops/SkIntersections.h index eced4dd15f..0186b3797d 100644 --- a/src/pathops/SkIntersections.h +++ b/src/pathops/SkIntersections.h @@ -21,8 +21,10 @@ public: #endif { sk_bzero(fPt, sizeof(fPt)); + sk_bzero(fPt2, sizeof(fPt2)); sk_bzero(fT, sizeof(fT)); sk_bzero(fIsCoincident, sizeof(fIsCoincident)); + sk_bzero(fNearlySame, sizeof(fNearlySame)); reset(); fMax = 0; // require that the caller set the max } @@ -37,16 +39,6 @@ public: }; TArray operator[](int n) const { return TArray(fT[n]); } - void set(const SkIntersections& i) { - memcpy(fPt, i.fPt, sizeof(fPt)); - memcpy(fT, i.fT, sizeof(fT)); - memcpy(fIsCoincident, i.fIsCoincident, sizeof(fIsCoincident)); - fUsed = i.fUsed; - fMax = i.fMax; - fSwap = i.fSwap; - SkDEBUGCODE(fDepth = i.fDepth); - } - void allowNear(bool nearAllowed) { fAllowNear = nearAllowed; } @@ -140,10 +132,19 @@ public: return intersect(aLine, bLine); } + bool nearlySame(int index) const { + SkASSERT(index == 0 || index == 1); + return fNearlySame[index]; + } + const SkDPoint& pt(int index) const { return fPt[index]; } + const SkDPoint& pt2(int index) const { + return fPt2[index]; + } + int quadHorizontal(const SkPoint a[3], SkScalar left, SkScalar right, SkScalar y, bool flipped) { SkDQuad quad; @@ -177,12 +178,16 @@ public: return intersect(aQuad, bQuad); } - // leaves flip, swap, max alone + // leaves swap, max alone void reset() { fAllowNear = true; fUsed = 0; } + void set(bool swap, int tIndex, double t) { + fT[(int) swap][tIndex] = t; + } + void setMax(int max) { fMax = max; } @@ -212,6 +217,8 @@ public: void append(const SkIntersections& ); void cleanUpCoincidence(); int coincidentUsed() const; + void cubicInsert(double one, double two, const SkDPoint& pt, const SkDCubic& c1, + const SkDCubic& c2); int cubicRay(const SkPoint pts[4], const SkDLine& line); void flip(); int horizontal(const SkDLine&, double y); @@ -223,7 +230,7 @@ public: int horizontal(const SkDCubic&, double left, double right, double y, double tRange[3]); // FIXME : does not respect swap int insert(double one, double two, const SkDPoint& pt); - void insertNear(double one, double two, const SkDPoint& pt); + void insertNear(double one, double two, const SkDPoint& pt1, const SkDPoint& pt2); // start if index == 0 : end if index == 1 void insertCoincident(double one, double two, const SkDPoint& pt); int intersect(const SkDLine&, const SkDLine&); @@ -267,8 +274,10 @@ private: void computePoints(const SkDLine& line, int used); SkDPoint fPt[9]; // FIXME: since scans store points as SkPoint, this should also + SkDPoint fPt2[9]; // used by nearly same to store alternate intersection point double fT[2][9]; uint16_t fIsCoincident[2]; // bit set for each curve's coincident T + bool fNearlySame[2]; // true if end points nearly match unsigned char fUsed; unsigned char fMax; bool fAllowNear; diff --git a/src/pathops/SkOpAngle.cpp b/src/pathops/SkOpAngle.cpp index 094b22c7e5..894758cfe8 100644 --- a/src/pathops/SkOpAngle.cpp +++ b/src/pathops/SkOpAngle.cpp @@ -289,7 +289,7 @@ bool SkOpAngle::computeSector() { // FIXME: logically, this should return !fUnorderable, but doing so breaks testQuadratic51 // -- but in general, this code may not work so this may be the least of problems // adding the bang fixes testQuads46x in release, however - return fUnorderable; + return !fUnorderable; } SkASSERT(fSegment->verb() != SkPath::kLine_Verb && small()); fComputedSector = true; @@ -322,7 +322,7 @@ recomputeSector: return false; } int saveEnd = fEnd; - fEnd = checkEnd - step; + fComputedEnd = fEnd = checkEnd - step; setSpans(); setSector(); fEnd = saveEnd; @@ -414,12 +414,12 @@ bool SkOpAngle::endsIntersect(const SkOpAngle& rh) const { SkIntersections i; (*CurveIntersectRay[index ? rPts : lPts])(segment.pts(), rays[index], &i); // SkASSERT(i.used() >= 1); - if (i.used() <= 1) { - continue; - } +// if (i.used() <= 1) { +// continue; +// } double tStart = segment.t(index ? rh.fStart : fStart); - double tEnd = segment.t(index ? rh.fEnd : fEnd); - bool testAscends = index ? rh.fStart < rh.fEnd : fStart < fEnd; + double tEnd = segment.t(index ? rh.fComputedEnd : fComputedEnd); + bool testAscends = index ? rh.fStart < rh.fComputedEnd : fStart < fComputedEnd; double t = testAscends ? 0 : 1; for (int idx2 = 0; idx2 < i.used(); ++idx2) { double testT = i[0][idx2]; @@ -879,12 +879,9 @@ SkOpAngle* SkOpAngle::previous() const { } void SkOpAngle::set(const SkOpSegment* segment, int start, int end) { -#if DEBUG_ANGLE - fID = 0; -#endif fSegment = segment; fStart = start; - fEnd = end; + fComputedEnd = fEnd = end; fNext = NULL; fComputeSector = fComputedSector = false; fStop = false; @@ -1097,3 +1094,33 @@ bool SkOpAngle::tangentsDiverge(const SkOpAngle& rh, double s0xt0) const { double mFactor = fabs(useS ? distEndRatio(sDist) : rh.distEndRatio(tDist)); return mFactor < 5000; // empirically found limit } + +SkOpAngleSet::SkOpAngleSet() + : fAngles(NULL) +#if DEBUG_ANGLE + , fCount(0) +#endif +{ +} + +SkOpAngleSet::~SkOpAngleSet() { + SkDELETE(fAngles); +} + +SkOpAngle& SkOpAngleSet::push_back() { + if (!fAngles) { + fAngles = SkNEW_ARGS(SkChunkAlloc, (2)); + } + void* ptr = fAngles->allocThrow(sizeof(SkOpAngle)); + SkOpAngle* angle = (SkOpAngle*) ptr; +#if DEBUG_ANGLE + angle->setID(++fCount); +#endif + return *angle; +} + +void SkOpAngleSet::reset() { + if (fAngles) { + fAngles->reset(); + } +} diff --git a/src/pathops/SkOpAngle.h b/src/pathops/SkOpAngle.h index e5669133e4..63d378d73c 100644 --- a/src/pathops/SkOpAngle.h +++ b/src/pathops/SkOpAngle.h @@ -7,6 +7,7 @@ #ifndef SkOpAngle_DEFINED #define SkOpAngle_DEFINED +#include "SkChunkAlloc.h" #include "SkLineParameters.h" class SkOpSegment; @@ -81,13 +82,19 @@ public: void debugSameAs(const SkOpAngle* compare) const; #endif void dump() const; - void dumpFromTo(const SkOpSegment* fromSeg, int from, int to) const; + void dumpLoop() const; + void dumpTo(const SkOpSegment* fromSeg, const SkOpAngle* ) const; #if DEBUG_ANGLE + int debugID() const { return fID; } + void setID(int id) { fID = id; } +#else + int debugID() const { return 0; } #endif + #if DEBUG_VALIDATE void debugValidateLoop() const; #endif @@ -121,6 +128,7 @@ private: SkDVector fSweep[2]; int fStart; int fEnd; + int fComputedEnd; int fSectorMask; int8_t fSectorStart; // in 32nds of a circle int8_t fSectorEnd; @@ -131,11 +139,7 @@ private: bool fComputeSector; bool fComputedSector; -#if DEBUG_SORT - void debugOne(bool showFunc) const; // available to testing only -#endif #if DEBUG_ANGLE - int debugID() const { return fID; } int fID; #endif #if DEBUG_VALIDATE @@ -143,9 +147,23 @@ private: #else void debugValidateNext() const {} #endif - void dumpLoop() const; // utility to be called by user from debugger + void dumpOne(bool showFunc) const; // available to testing only void dumpPartials() const; // utility to be called by user from debugger friend class PathOpsAngleTester; }; +class SkOpAngleSet { +public: + SkOpAngleSet(); + ~SkOpAngleSet(); + SkOpAngle& push_back(); + void reset(); +private: + void dump() const; // utility to be called by user from debugger +#if DEBUG_ANGLE + int fCount; +#endif + SkChunkAlloc* fAngles; +}; + #endif diff --git a/src/pathops/SkOpContour.cpp b/src/pathops/SkOpContour.cpp index e3137b756c..5ef702d4c1 100644 --- a/src/pathops/SkOpContour.cpp +++ b/src/pathops/SkOpContour.cpp @@ -28,8 +28,14 @@ bool SkOpContour::addCoincident(int index, SkOpContour* other, int otherIndex, coincidence.fTs[swap][1] = ts[0][1]; coincidence.fTs[!swap][0] = ts[1][0]; coincidence.fTs[!swap][1] = ts[1][1]; - coincidence.fPts[0] = pt0; - coincidence.fPts[1] = pt1; + coincidence.fPts[swap][0] = pt0; + coincidence.fPts[swap][1] = pt1; + bool nearStart = ts.nearlySame(0); + bool nearEnd = ts.nearlySame(1); + coincidence.fPts[!swap][0] = nearStart ? ts.pt2(0).asSkPoint() : pt0; + coincidence.fPts[!swap][1] = nearEnd ? ts.pt2(1).asSkPoint() : pt1; + coincidence.fNearly[0] = nearStart; + coincidence.fNearly[1] = nearEnd; return true; } @@ -93,28 +99,31 @@ void SkOpContour::addCoincidentPoints() { cancelers ^= true; } SkASSERT(!approximately_negative(oEndT - oStartT)); + const SkPoint& startPt = coincidence.fPts[0][startSwapped]; if (cancelers) { // make sure startT and endT have t entries - const SkPoint& startPt = coincidence.fPts[startSwapped]; if (startT > 0 || oEndT < 1 || thisOne.isMissing(startT, startPt) || other.isMissing(oEndT, startPt)) { - thisOne.addTPair(startT, &other, oEndT, true, startPt); + thisOne.addTPair(startT, &other, oEndT, true, startPt, + coincidence.fPts[1][startSwapped]); } - const SkPoint& oStartPt = coincidence.fPts[oStartSwapped]; + const SkPoint& oStartPt = coincidence.fPts[1][oStartSwapped]; if (oStartT > 0 || endT < 1 || thisOne.isMissing(endT, oStartPt) || other.isMissing(oStartT, oStartPt)) { - other.addTPair(oStartT, &thisOne, endT, true, oStartPt); + other.addTPair(oStartT, &thisOne, endT, true, oStartPt, + coincidence.fPts[0][oStartSwapped]); } } else { - const SkPoint& startPt = coincidence.fPts[startSwapped]; if (startT > 0 || oStartT > 0 || thisOne.isMissing(startT, startPt) || other.isMissing(oStartT, startPt)) { - thisOne.addTPair(startT, &other, oStartT, true, startPt); + thisOne.addTPair(startT, &other, oStartT, true, startPt, + coincidence.fPts[1][startSwapped]); } - const SkPoint& oEndPt = coincidence.fPts[!oStartSwapped]; + const SkPoint& oEndPt = coincidence.fPts[1][!oStartSwapped]; if (endT < 1 || oEndT < 1 || thisOne.isMissing(endT, oEndPt) || other.isMissing(oEndT, oEndPt)) { - other.addTPair(oEndT, &thisOne, endT, true, oEndPt); + other.addTPair(oEndT, &thisOne, endT, true, oEndPt, + coincidence.fPts[0][!oStartSwapped]); } } #if DEBUG_CONCIDENT @@ -122,6 +131,32 @@ void SkOpContour::addCoincidentPoints() { other.debugShowTs("o"); #endif } + // if there are multiple pairs of coincidence that share an edge, see if the opposite + // are also coincident + for (int index = 0; index < count - 1; ++index) { + const SkCoincidence& coincidence = fCoincidences[index]; + int thisIndex = coincidence.fSegments[0]; + SkOpContour* otherContour = coincidence.fOther; + int otherIndex = coincidence.fSegments[1]; + for (int idx2 = 1; idx2 < count; ++idx2) { + const SkCoincidence& innerCoin = fCoincidences[idx2]; + int innerThisIndex = innerCoin.fSegments[0]; + if (thisIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 1, innerCoin, 1, false); + } + if (this == otherContour && otherIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 0, innerCoin, 1, false); + } + SkOpContour* innerOtherContour = innerCoin.fOther; + innerThisIndex = innerCoin.fSegments[1]; + if (this == innerOtherContour && thisIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 1, innerCoin, 0, false); + } + if (otherContour == innerOtherContour && otherIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 0, innerCoin, 0, false); + } + } + } } bool SkOpContour::addPartialCoincident(int index, SkOpContour* other, int otherIndex, @@ -143,11 +178,77 @@ bool SkOpContour::addPartialCoincident(int index, SkOpContour* other, int otherI coincidence.fTs[swap][1] = ts[0][ptIndex + 1]; coincidence.fTs[!swap][0] = ts[1][ptIndex]; coincidence.fTs[!swap][1] = ts[1][ptIndex + 1]; - coincidence.fPts[0] = pt0; - coincidence.fPts[1] = pt1; + coincidence.fPts[0][0] = coincidence.fPts[1][0] = pt0; + coincidence.fPts[0][1] = coincidence.fPts[1][1] = pt1; + coincidence.fNearly[0] = 0; + coincidence.fNearly[1] = 0; return true; } +void SkOpContour::align(const SkOpSegment::AlignedSpan& aligned, bool swap, + SkCoincidence* coincidence) { + for (int idx2 = 0; idx2 < 2; ++idx2) { + if (coincidence->fPts[0][idx2] == aligned.fOldPt + && coincidence->fTs[swap][idx2] == aligned.fOldT) { + SkASSERT(SkDPoint::RoughlyEqual(coincidence->fPts[0][idx2], aligned.fPt)); + coincidence->fPts[0][idx2] = aligned.fPt; + SkASSERT(way_roughly_equal(coincidence->fTs[swap][idx2], aligned.fT)); + coincidence->fTs[swap][idx2] = aligned.fT; + } + } +} + +void SkOpContour::alignCoincidence(const SkOpSegment::AlignedSpan& aligned, + SkTArray<SkCoincidence, true>* coincidences) { + int count = coincidences->count(); + for (int index = 0; index < count; ++index) { + SkCoincidence& coincidence = (*coincidences)[index]; + int thisIndex = coincidence.fSegments[0]; + const SkOpSegment* thisOne = &fSegments[thisIndex]; + const SkOpContour* otherContour = coincidence.fOther; + int otherIndex = coincidence.fSegments[1]; + const SkOpSegment* other = &otherContour->fSegments[otherIndex]; + if (thisOne == aligned.fOther1 && other == aligned.fOther2) { + align(aligned, false, &coincidence); + } else if (thisOne == aligned.fOther2 && other == aligned.fOther1) { + align(aligned, true, &coincidence); + } + } +} + +void SkOpContour::alignTPt(int segmentIndex, const SkOpContour* other, int otherIndex, + bool swap, int tIndex, SkIntersections* ts, SkPoint* point) const { + int zeroPt; + if ((zeroPt = alignT(swap, tIndex, ts)) >= 0) { + alignPt(segmentIndex, point, zeroPt); + } + if ((zeroPt = other->alignT(!swap, tIndex, ts)) >= 0) { + other->alignPt(otherIndex, point, zeroPt); + } +} + +void SkOpContour::alignPt(int index, SkPoint* point, int zeroPt) const { + const SkOpSegment& segment = fSegments[index]; + if (0 == zeroPt) { + *point = segment.pts()[0]; + } else { + *point = segment.pts()[SkPathOpsVerbToPoints(segment.verb())]; + } +} + +int SkOpContour::alignT(bool swap, int tIndex, SkIntersections* ts) const { + double tVal = (*ts)[swap][tIndex]; + if (tVal != 0 && precisely_zero(tVal)) { + ts->set(swap, tIndex, 0); + return 0; + } + if (tVal != 1 && precisely_equal(tVal, 1)) { + ts->set(swap, tIndex, 1); + return 1; + } + return -1; +} + bool SkOpContour::calcAngles() { int segmentCount = fSegments.count(); for (int test = 0; test < segmentCount; ++test) { @@ -180,7 +281,187 @@ void SkOpContour::calcPartialCoincidentWinding() { #endif for (int index = 0; index < count; ++index) { SkCoincidence& coincidence = fPartialCoincidences[index]; - calcCommonCoincidentWinding(coincidence); + calcCommonCoincidentWinding(coincidence); + } + // if there are multiple pairs of partial coincidence that share an edge, see if the opposite + // are also coincident + for (int index = 0; index < count - 1; ++index) { + const SkCoincidence& coincidence = fPartialCoincidences[index]; + int thisIndex = coincidence.fSegments[0]; + SkOpContour* otherContour = coincidence.fOther; + int otherIndex = coincidence.fSegments[1]; + for (int idx2 = 1; idx2 < count; ++idx2) { + const SkCoincidence& innerCoin = fPartialCoincidences[idx2]; + int innerThisIndex = innerCoin.fSegments[0]; + if (thisIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 1, innerCoin, 1, true); + } + if (this == otherContour && otherIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 0, innerCoin, 1, true); + } + SkOpContour* innerOtherContour = innerCoin.fOther; + innerThisIndex = innerCoin.fSegments[1]; + if (this == innerOtherContour && thisIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 1, innerCoin, 0, true); + } + if (otherContour == innerOtherContour && otherIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 0, innerCoin, 0, true); + } + } + } +} + +void SkOpContour::checkCoincidentPair(const SkCoincidence& oneCoin, int oneIdx, + const SkCoincidence& twoCoin, int twoIdx, bool partial) { + SkASSERT((oneIdx ? this : oneCoin.fOther) == (twoIdx ? this : twoCoin.fOther)); + SkASSERT(oneCoin.fSegments[!oneIdx] == twoCoin.fSegments[!twoIdx]); + // look for common overlap + double min = SK_ScalarMax; + double max = SK_ScalarMin; + double min1 = oneCoin.fTs[!oneIdx][0]; + double max1 = oneCoin.fTs[!oneIdx][1]; + double min2 = twoCoin.fTs[!twoIdx][0]; + double max2 = twoCoin.fTs[!twoIdx][1]; + bool cancelers = (min1 < max1) != (min2 < max2); + if (min1 > max1) { + SkTSwap(min1, max1); + } + if (min2 > max2) { + SkTSwap(min2, max2); + } + if (between(min1, min2, max1)) { + min = min2; + } + if (between(min1, max2, max1)) { + max = max2; + } + if (between(min2, min1, max2)) { + min = SkTMin(min, min1); + } + if (between(min2, max1, max2)) { + max = SkTMax(max, max1); + } + if (min >= max) { + return; // no overlap + } + // look to see if opposite are different segments + int seg1Index = oneCoin.fSegments[oneIdx]; + int seg2Index = twoCoin.fSegments[twoIdx]; + if (seg1Index == seg2Index) { + return; + } + SkOpContour* contour1 = oneIdx ? oneCoin.fOther : this; + SkOpContour* contour2 = twoIdx ? twoCoin.fOther : this; + SkOpSegment* segment1 = &contour1->fSegments[seg1Index]; + SkOpSegment* segment2 = &contour2->fSegments[seg2Index]; + // find opposite t value ranges corresponding to reference min/max range + const SkOpContour* refContour = oneIdx ? this : oneCoin.fOther; + const int refSegIndex = oneCoin.fSegments[!oneIdx]; + const SkOpSegment* refSegment = &refContour->fSegments[refSegIndex]; + int seg1Start = segment1->findOtherT(min, refSegment); + int seg1End = segment1->findOtherT(max, refSegment); + int seg2Start = segment2->findOtherT(min, refSegment); + int seg2End = segment2->findOtherT(max, refSegment); + // if the opposite pairs already contain min/max, we're done + if (seg1Start >= 0 && seg1End >= 0 && seg2Start >= 0 && seg2End >= 0) { + return; + } + double loEnd = SkTMin(min1, min2); + double hiEnd = SkTMax(max1, max2); + // insert the missing coincident point(s) + double missingT1 = -1; + double otherT1 = -1; + if (seg1Start < 0) { + if (seg2Start < 0) { + return; + } + missingT1 = segment1->calcMissingTStart(refSegment, loEnd, min, max, hiEnd, + segment2, seg1End); + if (missingT1 < 0) { + return; + } + const SkOpSpan* missingSpan = &segment2->span(seg2Start); + otherT1 = missingSpan->fT; + } else if (seg2Start < 0) { + SkASSERT(seg1Start >= 0); + missingT1 = segment2->calcMissingTStart(refSegment, loEnd, min, max, hiEnd, + segment1, seg2End); + if (missingT1 < 0) { + return; + } + const SkOpSpan* missingSpan = &segment1->span(seg1Start); + otherT1 = missingSpan->fT; + } + SkPoint missingPt1; + SkOpSegment* addTo1 = NULL; + SkOpSegment* addOther1 = seg1Start < 0 ? segment2 : segment1; + int minTIndex = refSegment->findExactT(min, addOther1); + SkASSERT(minTIndex >= 0); + if (missingT1 >= 0) { + missingPt1 = refSegment->span(minTIndex).fPt; + addTo1 = seg1Start < 0 ? segment1 : segment2; + } + double missingT2 = -1; + double otherT2 = -1; + if (seg1End < 0) { + if (seg2End < 0) { + return; + } + missingT2 = segment1->calcMissingTEnd(refSegment, loEnd, min, max, hiEnd, + segment2, seg1Start); + if (missingT2 < 0) { + return; + } + const SkOpSpan* missingSpan = &segment2->span(seg2End); + otherT2 = missingSpan->fT; + } else if (seg2End < 0) { + SkASSERT(seg1End >= 0); + missingT2 = segment2->calcMissingTEnd(refSegment, loEnd, min, max, hiEnd, + segment1, seg2Start); + if (missingT2 < 0) { + return; + } + const SkOpSpan* missingSpan = &segment1->span(seg1End); + otherT2 = missingSpan->fT; + } + SkPoint missingPt2; + SkOpSegment* addTo2 = NULL; + SkOpSegment* addOther2 = seg1End < 0 ? segment2 : segment1; + int maxTIndex = refSegment->findExactT(max, addOther2); + SkASSERT(maxTIndex >= 0); + if (missingT2 >= 0) { + missingPt2 = refSegment->span(maxTIndex).fPt; + addTo2 = seg1End < 0 ? segment1 : segment2; + } + if (missingT1 >= 0) { + addTo1->pinT(missingPt1, &missingT1); + addTo1->addTPair(missingT1, addOther1, otherT1, false, missingPt1); + } else { + SkASSERT(minTIndex >= 0); + missingPt1 = refSegment->span(minTIndex).fPt; + } + if (missingT2 >= 0) { + addTo2->pinT(missingPt2, &missingT2); + addTo2->addTPair(missingT2, addOther2, otherT2, false, missingPt2); + } else { + SkASSERT(minTIndex >= 0); + missingPt2 = refSegment->span(maxTIndex).fPt; + } + if (!partial) { + return; + } + if (cancelers) { + if (missingT1 >= 0) { + addTo1->addTCancel(missingPt1, missingPt2, addOther1); + } else { + addTo2->addTCancel(missingPt1, missingPt2, addOther2); + } + } else if (missingT1 >= 0) { + addTo1->addTCoincident(missingPt1, missingPt2, addTo1 == addTo2 ? missingT2 : otherT2, + addOther1); + } else { + addTo2->addTCoincident(missingPt2, missingPt1, addTo2 == addTo1 ? missingT1 : otherT1, + addOther2); } } @@ -196,9 +477,15 @@ void SkOpContour::joinCoincidence(const SkTArray<SkCoincidence, true>& coinciden const SkCoincidence& coincidence = coincidences[index]; int thisIndex = coincidence.fSegments[0]; SkOpSegment& thisOne = fSegments[thisIndex]; + if (thisOne.done()) { + continue; + } SkOpContour* otherContour = coincidence.fOther; int otherIndex = coincidence.fSegments[1]; SkOpSegment& other = otherContour->fSegments[otherIndex]; + if (other.done()) { + continue; + } double startT = coincidence.fTs[0][0]; double endT = coincidence.fTs[0][1]; if (startT == endT) { // this can happen in very large compares @@ -211,8 +498,8 @@ void SkOpContour::joinCoincidence(const SkTArray<SkCoincidence, true>& coinciden } bool swapStart = startT > endT; bool swapOther = oStartT > oEndT; - const SkPoint* startPt = &coincidence.fPts[0]; - const SkPoint* endPt = &coincidence.fPts[1]; + const SkPoint* startPt = &coincidence.fPts[0][0]; + const SkPoint* endPt = &coincidence.fPts[0][1]; if (swapStart) { SkTSwap(startT, endT); SkTSwap(oStartT, oEndT); @@ -243,6 +530,9 @@ void SkOpContour::joinCoincidence(const SkTArray<SkCoincidence, true>& coinciden } void SkOpContour::calcCommonCoincidentWinding(const SkCoincidence& coincidence) { + if (coincidence.fNearly[0] && coincidence.fNearly[1]) { + return; + } int thisIndex = coincidence.fSegments[0]; SkOpSegment& thisOne = fSegments[thisIndex]; if (thisOne.done()) { @@ -256,8 +546,8 @@ void SkOpContour::calcCommonCoincidentWinding(const SkCoincidence& coincidence) } double startT = coincidence.fTs[0][0]; double endT = coincidence.fTs[0][1]; - const SkPoint* startPt = &coincidence.fPts[0]; - const SkPoint* endPt = &coincidence.fPts[1]; + const SkPoint* startPt = &coincidence.fPts[0][0]; + const SkPoint* endPt = &coincidence.fPts[0][1]; bool cancelers; if ((cancelers = startT > endT)) { SkTSwap<double>(startT, endT); @@ -291,6 +581,57 @@ void SkOpContour::calcCommonCoincidentWinding(const SkCoincidence& coincidence) #endif } +void SkOpContour::resolveNearCoincidence() { + int count = fCoincidences.count(); + for (int index = 0; index < count; ++index) { + SkCoincidence& coincidence = fCoincidences[index]; + if (!coincidence.fNearly[0] || !coincidence.fNearly[1]) { + continue; + } + int thisIndex = coincidence.fSegments[0]; + SkOpSegment& thisOne = fSegments[thisIndex]; + SkOpContour* otherContour = coincidence.fOther; + int otherIndex = coincidence.fSegments[1]; + SkOpSegment& other = otherContour->fSegments[otherIndex]; + if ((thisOne.done() || other.done()) && thisOne.complete() && other.complete()) { + // OPTIMIZATION: remove from coincidence array + continue; + } + #if DEBUG_CONCIDENT + thisOne.debugShowTs("-"); + other.debugShowTs("o"); + #endif + double startT = coincidence.fTs[0][0]; + double endT = coincidence.fTs[0][1]; + bool cancelers; + if ((cancelers = startT > endT)) { + SkTSwap<double>(startT, endT); + } + if (startT == endT) { // if span is very large, the smaller may have collapsed to nothing + if (endT <= 1 - FLT_EPSILON) { + endT += FLT_EPSILON; + SkASSERT(endT <= 1); + } else { + startT -= FLT_EPSILON; + SkASSERT(startT >= 0); + } + } + SkASSERT(!approximately_negative(endT - startT)); + double oStartT = coincidence.fTs[1][0]; + double oEndT = coincidence.fTs[1][1]; + if (oStartT > oEndT) { + SkTSwap<double>(oStartT, oEndT); + cancelers ^= true; + } + SkASSERT(!approximately_negative(oEndT - oStartT)); + if (cancelers) { + thisOne.blindCancel(coincidence, &other); + } else { + thisOne.blindCoincident(coincidence, &other); + } + } +} + void SkOpContour::sortAngles() { int segmentCount = fSegments.count(); for (int test = 0; test < segmentCount; ++test) { diff --git a/src/pathops/SkOpContour.h b/src/pathops/SkOpContour.h index 7fad7a40e4..d1b3cd0179 100644 --- a/src/pathops/SkOpContour.h +++ b/src/pathops/SkOpContour.h @@ -22,7 +22,8 @@ struct SkCoincidence { SkOpContour* fOther; int fSegments[2]; double fTs[2][2]; - SkPoint fPts[2]; + SkPoint fPts[2][2]; + int fNearly[2]; }; class SkOpContour { @@ -86,6 +87,28 @@ public: return fSegments[segIndex].addSelfT(pt, newT); } + void align(const SkOpSegment::AlignedSpan& aligned, bool swap, SkCoincidence* coincidence); + void alignCoincidence(const SkOpSegment::AlignedSpan& aligned, + SkTArray<SkCoincidence, true>* coincidences); + + void alignCoincidence(const SkOpSegment::AlignedSpan& aligned) { + alignCoincidence(aligned, &fCoincidences); + alignCoincidence(aligned, &fPartialCoincidences); + } + + void alignMultiples(SkTDArray<SkOpSegment::AlignedSpan>* aligned) { + int segmentCount = fSegments.count(); + for (int sIndex = 0; sIndex < segmentCount; ++sIndex) { + SkOpSegment& segment = fSegments[sIndex]; + if (segment.hasMultiples()) { + segment.alignMultiples(aligned); + } + } + } + + void alignTPt(int segmentIndex, const SkOpContour* other, int otherIndex, + bool swap, int tIndex, SkIntersections* ts, SkPoint* point) const; + const SkPathOpsBounds& bounds() const { return fBounds; } @@ -127,6 +150,7 @@ public: SkOpSegment& segment = fSegments[sIndex]; if (segment.count() > 2) { segment.checkMultiples(); + fMultiples |= segment.hasMultiples(); } } } @@ -135,6 +159,7 @@ public: int segmentCount = fSegments.count(); for (int sIndex = 0; sIndex < segmentCount; ++sIndex) { SkOpSegment& segment = fSegments[sIndex]; + // OPTIMIZATION : skip segments that are done? if (segment.hasSmall()) { segment.checkSmall(); } @@ -189,6 +214,10 @@ public: } } + bool hasMultiples() const { + return fMultiples; + } + void joinCoincidence() { joinCoincidence(fCoincidences, false); joinCoincidence(fPartialCoincidences, true); @@ -203,9 +232,11 @@ public: void reset() { fSegments.reset(); fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax); - fContainsCurves = fContainsCubics = fContainsIntercepts = fDone = false; + fContainsCurves = fContainsCubics = fContainsIntercepts = fDone = fMultiples = false; } + void resolveNearCoincidence(); + SkTArray<SkOpSegment>& segments() { return fSegments; } @@ -284,11 +315,19 @@ public: // available to test routines only void dump() const; void dumpAngles() const; + void dumpCoincidence(const SkCoincidence& ) const; + void dumpCoincidences() const; + void dumpPt(int ) const; void dumpPts() const; + void dumpSpan(int ) const; void dumpSpans() const; private: + void alignPt(int index, SkPoint* point, int zeroPt) const; + int alignT(bool swap, int tIndex, SkIntersections* ts) const; void calcCommonCoincidentWinding(const SkCoincidence& ); + void checkCoincidentPair(const SkCoincidence& oneCoin, int oneIdx, + const SkCoincidence& twoCoin, int twoIdx, bool partial); void joinCoincidence(const SkTArray<SkCoincidence, true>& , bool partial); void setBounds(); @@ -303,6 +342,7 @@ private: bool fContainsCubics; bool fContainsCurves; bool fDone; + bool fMultiples; // set if some segment has multiple identical intersections with other curves bool fOperand; // true for the second argument to a binary operator bool fXor; bool fOppXor; diff --git a/src/pathops/SkOpSegment.cpp b/src/pathops/SkOpSegment.cpp index 0e48b3f913..59e6d344a5 100644 --- a/src/pathops/SkOpSegment.cpp +++ b/src/pathops/SkOpSegment.cpp @@ -5,6 +5,7 @@ * found in the LICENSE file. */ #include "SkIntersections.h" +#include "SkOpContour.h" #include "SkOpSegment.h" #include "SkPathWriter.h" #include "SkTSort.h" @@ -187,7 +188,8 @@ bool SkOpSegment::activeOp(int xorMiMask, int xorSuMask, int index, int endIndex } bool result = gActiveEdge[op][miFrom][miTo][suFrom][suTo]; #if DEBUG_ACTIVE_OP - SkDebugf("%s op=%s miFrom=%d miTo=%d suFrom=%d suTo=%d result=%d\n", __FUNCTION__, + SkDebugf("%s id=%d t=%1.9g tEnd=%1.9g op=%s miFrom=%d miTo=%d suFrom=%d suTo=%d result=%d\n", + __FUNCTION__, debugID(), span(index).fT, span(endIndex).fT, SkPathOpsDebug::kPathOpStr[op], miFrom, miTo, suFrom, suTo, result); #endif return result; @@ -404,25 +406,24 @@ void SkOpSegment::addCurveTo(int start, int end, SkPathWriter* path, bool active void SkOpSegment::addEndSpan(int endIndex) { int spanCount = fTs.count(); - int angleIndex = fAngles.count(); int startIndex = endIndex - 1; while (fTs[startIndex].fT == 1 || fTs[startIndex].fTiny) { ++startIndex; SkASSERT(startIndex < spanCount - 1); ++endIndex; } - fAngles.push_back().set(this, spanCount - 1, startIndex); + SkOpAngle& angle = fAngles.push_back(); + angle.set(this, spanCount - 1, startIndex); #if DEBUG_ANGLE - fAngles.back().setID(angleIndex); debugCheckPointsEqualish(endIndex, spanCount); #endif - setFromAngleIndex(endIndex, angleIndex); + setFromAngle(endIndex, &angle); } -void SkOpSegment::setFromAngleIndex(int endIndex, int angleIndex) { +void SkOpSegment::setFromAngle(int endIndex, SkOpAngle* angle) { int spanCount = fTs.count(); do { - fTs[endIndex].fFromAngleIndex = angleIndex; + fTs[endIndex].fFromAngle = angle; } while (++endIndex < spanCount); } @@ -448,89 +449,92 @@ void SkOpSegment::addQuad(const SkPoint pts[3], bool operand, bool evenOdd) { fBounds.setQuadBounds(pts); } -int SkOpSegment::addSingletonAngleDown(int endIndex, SkOpSegment** otherPtr) { - int startIndex = findEndSpan(endIndex); - SkASSERT(startIndex > 0); - int spanIndex = endIndex - 1; - fSingletonAngles.push_back().set(this, spanIndex, startIndex - 1); - setFromAngleIndex(startIndex, fAngles.count() + fSingletonAngles.count() - 1); +SkOpAngle* SkOpSegment::addSingletonAngleDown(SkOpSegment** otherPtr, SkOpAngle** anglePtr) { + int spanIndex = count() - 1; + int startIndex = nextExactSpan(spanIndex, -1); + SkASSERT(startIndex >= 0); + SkOpAngle& angle = fAngles.push_back(); + *anglePtr = ∠ + angle.set(this, spanIndex, startIndex); + setFromAngle(spanIndex, &angle); SkOpSegment* other; + int oStartIndex, oEndIndex; do { - other = fTs[spanIndex].fOther; - if (other->fTs[0].fWindValue) { + const SkOpSpan& span = fTs[spanIndex]; + SkASSERT(span.fT > 0); + other = span.fOther; + oStartIndex = span.fOtherIndex; + oEndIndex = other->nextExactSpan(oStartIndex, 1); + if (oEndIndex > 0 && other->span(oStartIndex).fWindValue) { break; } + oEndIndex = oStartIndex; + oStartIndex = other->nextExactSpan(oEndIndex, -1); --spanIndex; - SkASSERT(fTs[spanIndex].fT == 1); - } while (true); - int oEndIndex = other->findStartSpan(0); - SkASSERT(oEndIndex > 0); - int otherIndex = other->fSingletonAngles.count(); - other->fSingletonAngles.push_back().set(other, 0, oEndIndex); - other->setToAngleIndex(oEndIndex, other->fAngles.count() + otherIndex); + } while (oStartIndex < 0 || !other->span(oStartIndex).fWindSum); + SkOpAngle& oAngle = other->fAngles.push_back(); + oAngle.set(other, oStartIndex, oEndIndex); + other->setToAngle(oEndIndex, &oAngle); *otherPtr = other; - return otherIndex; + return &oAngle; } -int SkOpSegment::addSingletonAngleUp(int start, SkOpSegment** otherPtr) { - int endIndex = findStartSpan(start); +SkOpAngle* SkOpSegment::addSingletonAngleUp(SkOpSegment** otherPtr, SkOpAngle** anglePtr) { + int endIndex = nextExactSpan(0, 1); SkASSERT(endIndex > 0); - int thisIndex = fSingletonAngles.count(); - fSingletonAngles.push_back().set(this, start, endIndex); - setToAngleIndex(endIndex, fAngles.count() + thisIndex); - int spanIndex = start; + SkOpAngle& angle = fAngles.push_back(); + *anglePtr = ∠ + angle.set(this, 0, endIndex); + setToAngle(endIndex, &angle); + int spanIndex = 0; SkOpSegment* other; - int oCount, oStartIndex; + int oStartIndex, oEndIndex; do { - other = fTs[spanIndex].fOther; - oCount = other->count(); - oStartIndex = other->findEndSpan(oCount); - SkASSERT(oStartIndex > 0); - if (other->fTs[oStartIndex - 1].fWindValue) { + const SkOpSpan& span = fTs[spanIndex]; + SkASSERT(span.fT < 1); + other = span.fOther; + oEndIndex = span.fOtherIndex; + oStartIndex = other->nextExactSpan(oEndIndex, -1); + if (oStartIndex >= 0 && other->span(oStartIndex).fWindValue) { break; } + oStartIndex = oEndIndex; + oEndIndex = other->nextExactSpan(oStartIndex, 1); ++spanIndex; - SkASSERT(fTs[spanIndex].fT == 0); - } while (true); - int otherIndex = other->fSingletonAngles.count(); - other->fSingletonAngles.push_back().set(other, oCount - 1, oStartIndex - 1); - other->setFromAngleIndex(oStartIndex, other->fAngles.count() + otherIndex); + } while (oEndIndex < 0 || !other->span(oStartIndex).fWindValue); + SkOpAngle& oAngle = other->fAngles.push_back(); + oAngle.set(other, oEndIndex, oStartIndex); + other->setFromAngle(oEndIndex, &oAngle); *otherPtr = other; - return otherIndex; + return &oAngle; } -SkOpAngle* SkOpSegment::addSingletonAngles(int start, int step) { - int thisIndex = fSingletonAngles.count(); +SkOpAngle* SkOpSegment::addSingletonAngles(int step) { SkOpSegment* other; - int otherIndex; + SkOpAngle* angle, * otherAngle; if (step > 0) { - otherIndex = addSingletonAngleUp(start, &other); + otherAngle = addSingletonAngleUp(&other, &angle); } else { - otherIndex = addSingletonAngleDown(start + 1, &other); + otherAngle = addSingletonAngleDown(&other, &angle); } - fSingletonAngles[thisIndex].insert(&other->fSingletonAngles[otherIndex]); -#if DEBUG_ANGLE - fSingletonAngles[thisIndex].setID(fAngles.count() + thisIndex); - other->fSingletonAngles[otherIndex].setID(other->fAngles.count() + otherIndex); -#endif - return &fSingletonAngles[thisIndex]; + angle->insert(otherAngle); + return angle; } void SkOpSegment::addStartSpan(int endIndex) { - int angleIndex = fAngles.count(); int index = 0; - fAngles.push_back().set(this, index, endIndex); + SkOpAngle& angle = fAngles.push_back(); + angle.set(this, index, endIndex); #if DEBUG_ANGLE - fAngles.back().setID(angleIndex); debugCheckPointsEqualish(index, endIndex); #endif - setToAngleIndex(endIndex, angleIndex); + setToAngle(endIndex, &angle); } -void SkOpSegment::setToAngleIndex(int endIndex, int angleIndex) { +void SkOpSegment::setToAngle(int endIndex, SkOpAngle* angle) { int index = 0; do { - fTs[index].fToAngleIndex = angleIndex; + fTs[index].fToAngle = angle; } while (++index < endIndex); } @@ -546,17 +550,14 @@ int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) { #if 0 // this needs an even rougher association to be useful SkASSERT(SkDPoint::RoughlyEqual(ptAtT(newT), pt)); #endif - if (precisely_zero(newT)) { - newT = 0; - } else if (precisely_equal(newT, 1)) { - newT = 1; - } + const SkPoint& firstPt = fPts[0]; + const SkPoint& lastPt = fPts[SkPathOpsVerbToPoints(fVerb)]; + SkASSERT(newT == 0 || !precisely_zero(newT)); + SkASSERT(newT == 1 || !precisely_equal(newT, 1)); // FIXME: in the pathological case where there is a ton of intercepts, // binary search? int insertedAt = -1; int tCount = fTs.count(); - const SkPoint& firstPt = fPts[0]; - const SkPoint& lastPt = fPts[SkPathOpsVerbToPoints(fVerb)]; for (int index = 0; index < tCount; ++index) { // OPTIMIZATION: if there are three or more identical Ts, then // the fourth and following could be further insertion-sorted so @@ -588,6 +589,9 @@ int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) { span = fTs.append(); } span->fT = newT; +#if SK_DEBUG + span->fOtherT = -1; +#endif span->fOther = other; span->fPt = pt; #if 0 @@ -595,20 +599,24 @@ int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) { SkASSERT(approximately_equal(xyAtT(newT).fX, pt.fX) && approximately_equal(xyAtT(newT).fY, pt.fY)); #endif - span->fFromAngleIndex = -1; - span->fToAngleIndex = -1; + span->fFromAngle = NULL; + span->fToAngle = NULL; span->fWindSum = SK_MinS32; span->fOppSum = SK_MinS32; span->fWindValue = 1; span->fOppValue = 0; span->fChased = false; - if ((span->fDone = newT == 1)) { - ++fDoneSpans; - } + span->fCoincident = false; span->fLoop = false; + span->fNear = false; + span->fMultiple = false; span->fSmall = false; span->fTiny = false; + if ((span->fDone = newT == 1)) { + ++fDoneSpans; + } int less = -1; +// FIXME: note that this relies on spans being a continguous array // find range of spans with nearly the same point as this one while (&span[less + 1] - fTs.begin() > 0 && AlmostEqualUlps(span[less].fPt, pt)) { if (fVerb == SkPath::kCubic_Verb) { @@ -687,6 +695,7 @@ void SkOpSegment::addTCancel(const SkPoint& startPt, const SkPoint& endPt, SkOpS while (index > 0 && precisely_equal(fTs[index].fT, fTs[index - 1].fT)) { --index; } + bool oFoundEnd = false; int oIndex = other->fTs.count(); while (startPt != other->fTs[--oIndex].fPt) { // look for startPt match SkASSERT(oIndex > 0); @@ -700,15 +709,19 @@ void SkOpSegment::addTCancel(const SkPoint& startPt, const SkPoint& endPt, SkOpS SkOpSpan* oTest = &other->fTs[oIndex]; SkSTArray<kOutsideTrackedTCount, SkPoint, true> outsidePts; SkSTArray<kOutsideTrackedTCount, SkPoint, true> oOutsidePts; + bool decrement, track, bigger; + int originalWindValue; + const SkPoint* testPt; + const SkPoint* oTestPt; do { SkASSERT(test->fT < 1); SkASSERT(oTest->fT < 1); - bool decrement = test->fWindValue && oTest->fWindValue; - bool track = test->fWindValue || oTest->fWindValue; - bool bigger = test->fWindValue >= oTest->fWindValue; - const SkPoint& testPt = test->fPt; + decrement = test->fWindValue && oTest->fWindValue; + track = test->fWindValue || oTest->fWindValue; + bigger = test->fWindValue >= oTest->fWindValue; + testPt = &test->fPt; double testT = test->fT; - const SkPoint& oTestPt = oTest->fPt; + oTestPt = &oTest->fPt; double oTestT = oTest->fT; do { if (decrement) { @@ -718,12 +731,12 @@ void SkOpSegment::addTCancel(const SkPoint& startPt, const SkPoint& endPt, SkOpS decrementSpan(test); } } else if (track) { - TrackOutsidePair(&outsidePts, testPt, oTestPt); + TrackOutsidePair(&outsidePts, *testPt, *oTestPt); } SkASSERT(index < fTs.count() - 1); test = &fTs[++index]; - } while (testPt == test->fPt || precisely_equal(testT, test->fT)); - SkDEBUGCODE(int originalWindValue = oTest->fWindValue); + } while (*testPt == test->fPt || precisely_equal(testT, test->fT)); + originalWindValue = oTest->fWindValue; do { SkASSERT(oTest->fT < 1); SkASSERT(originalWindValue == oTest->fWindValue); @@ -734,15 +747,48 @@ void SkOpSegment::addTCancel(const SkPoint& startPt, const SkPoint& endPt, SkOpS other->decrementSpan(oTest); } } else if (track) { - TrackOutsidePair(&oOutsidePts, oTestPt, testPt); + TrackOutsidePair(&oOutsidePts, *oTestPt, *testPt); } if (!oIndex) { break; } + oFoundEnd |= endPt == oTest->fPt; oTest = &other->fTs[--oIndex]; - } while (oTestPt == oTest->fPt || precisely_equal(oTestT, oTest->fT)); + } while (*oTestPt == oTest->fPt || precisely_equal(oTestT, oTest->fT)); } while (endPt != test->fPt && test->fT < 1); // FIXME: determine if canceled edges need outside ts added + if (!oFoundEnd) { + for (int oIdx2 = oIndex; oIdx2 >= 0; --oIdx2) { + SkOpSpan* oTst2 = &other->fTs[oIdx2]; + if (originalWindValue != oTst2->fWindValue) { + goto skipAdvanceOtherCancel; + } + if (!oTst2->fWindValue) { + goto skipAdvanceOtherCancel; + } + if (endPt == other->fTs[oIdx2].fPt) { + break; + } + } + do { + SkASSERT(originalWindValue == oTest->fWindValue); + if (decrement) { + if (binary && !bigger) { + oTest->fOppValue--; + } else { + other->decrementSpan(oTest); + } + } else if (track) { + TrackOutsidePair(&oOutsidePts, *oTestPt, *testPt); + } + if (!oIndex) { + break; + } + oTest = &other->fTs[--oIndex]; + oFoundEnd |= endPt == oTest->fPt; + } while (!oFoundEnd || endPt == oTest->fPt); + } +skipAdvanceOtherCancel: int outCount = outsidePts.count(); if (!done() && outCount) { addCancelOutsides(outsidePts[0], outsidePts[1], other); @@ -753,6 +799,8 @@ void SkOpSegment::addTCancel(const SkPoint& startPt, const SkPoint& endPt, SkOpS if (!other->done() && oOutsidePts.count()) { other->addCancelOutsides(oOutsidePts[0], oOutsidePts[1], this); } + setCoincidentRange(startPt, endPt, other); + other->setCoincidentRange(startPt, endPt, this); } int SkOpSegment::addSelfT(const SkPoint& pt, double newT) { @@ -763,48 +811,153 @@ int SkOpSegment::addSelfT(const SkPoint& pt, double newT) { return result; } +// find the starting or ending span with an existing loop of angles +// FIXME? replicate for all identical starting/ending spans? +// OPTIMIZE? remove the spans pointing to windValue==0 here or earlier? +// FIXME? assert that only one other span has a valid windValue or oppValue void SkOpSegment::addSimpleAngle(int index) { + SkOpSpan* span = &fTs[index]; if (index == 0) { - SkASSERT(!fTs[index].fTiny && fTs[index + 1].fT > 0); + do { + if (span->fToAngle) { + SkASSERT(span->fToAngle->loopCount() == 2); + SkASSERT(!span->fFromAngle); + span->fFromAngle = span->fToAngle->next(); + return; + } + span = &fTs[++index]; + } while (span->fT == 0); + SkASSERT(index == 1); + index = 0; + SkASSERT(!fTs[0].fTiny && fTs[1].fT > 0); addStartSpan(1); } else { + do { + if (span->fFromAngle) { + SkASSERT(span->fFromAngle->loopCount() == 2); + SkASSERT(!span->fToAngle); + span->fToAngle = span->fFromAngle->next(); + return; + } + span = &fTs[--index]; + } while (span->fT == 1); + SkASSERT(index == count() - 2); + index = count() - 1; SkASSERT(!fTs[index - 1].fTiny && fTs[index - 1].fT < 1); addEndSpan(index); } - SkOpSpan& span = fTs[index]; - SkOpSegment* other = span.fOther; - SkOpSpan& oSpan = other->fTs[span.fOtherIndex]; + span = &fTs[index]; + SkOpSegment* other = span->fOther; + SkOpSpan& oSpan = other->fTs[span->fOtherIndex]; SkOpAngle* angle, * oAngle; if (index == 0) { - SkASSERT(span.fOtherIndex - 1 >= 0); - SkASSERT(span.fOtherT == 1); - SkDEBUGCODE(SkOpSpan& oPrior = other->fTs[span.fOtherIndex - 1]); + SkASSERT(span->fOtherIndex - 1 >= 0); + SkASSERT(span->fOtherT == 1); + SkDEBUGCODE(SkOpSpan& oPrior = other->fTs[span->fOtherIndex - 1]); SkASSERT(!oPrior.fTiny && oPrior.fT < 1); - other->addEndSpan(span.fOtherIndex); - angle = &this->angle(span.fToAngleIndex); - oAngle = &other->angle(oSpan.fFromAngleIndex); + other->addEndSpan(span->fOtherIndex); + angle = span->fToAngle; + oAngle = oSpan.fFromAngle; } else { - SkASSERT(span.fOtherIndex + 1 < other->count()); - SkASSERT(span.fOtherT == 0); - SkASSERT(!oSpan.fTiny && (other->fTs[span.fOtherIndex + 1].fT > 0 - || (other->fTs[span.fOtherIndex + 1].fFromAngleIndex < 0 - && other->fTs[span.fOtherIndex + 1].fToAngleIndex < 0))); + SkASSERT(span->fOtherIndex + 1 < other->count()); + SkASSERT(span->fOtherT == 0); + SkASSERT(!oSpan.fTiny && (other->fTs[span->fOtherIndex + 1].fT > 0 + || (other->fTs[span->fOtherIndex + 1].fFromAngle == NULL + && other->fTs[span->fOtherIndex + 1].fToAngle == NULL))); int oIndex = 1; do { const SkOpSpan& osSpan = other->span(oIndex); - if (osSpan.fFromAngleIndex >= 0 || osSpan.fT > 0) { + if (osSpan.fFromAngle || osSpan.fT > 0) { break; } ++oIndex; SkASSERT(oIndex < other->count()); } while (true); other->addStartSpan(oIndex); - angle = &this->angle(span.fFromAngleIndex); - oAngle = &other->angle(oSpan.fToAngleIndex); + angle = span->fFromAngle; + oAngle = oSpan.fToAngle; } angle->insert(oAngle); } +void SkOpSegment::alignMultiples(SkTDArray<AlignedSpan>* alignedArray) { + debugValidate(); + int count = this->count(); + for (int index = 0; index < count; ++index) { + SkOpSpan& span = fTs[index]; + if (!span.fMultiple) { + continue; + } + int end = nextExactSpan(index, 1); + SkASSERT(end > index + 1); + const SkPoint& thisPt = span.fPt; + while (index < end - 1) { + SkOpSegment* other1 = span.fOther; + int oCnt = other1->count(); + for (int idx2 = index + 1; idx2 < end; ++idx2) { + SkOpSpan& span2 = fTs[idx2]; + SkOpSegment* other2 = span2.fOther; + for (int oIdx = 0; oIdx < oCnt; ++oIdx) { + SkOpSpan& oSpan = other1->fTs[oIdx]; + if (oSpan.fOther != other2) { + continue; + } + if (oSpan.fPt == thisPt) { + goto skipExactMatches; + } + } + for (int oIdx = 0; oIdx < oCnt; ++oIdx) { + SkOpSpan& oSpan = other1->fTs[oIdx]; + if (oSpan.fOther != other2) { + continue; + } + if (SkDPoint::RoughlyEqual(oSpan.fPt, thisPt)) { + SkOpSpan& oSpan2 = other2->fTs[oSpan.fOtherIndex]; + if (zero_or_one(span.fOtherT) || zero_or_one(oSpan.fT) + || zero_or_one(span2.fOtherT) || zero_or_one(oSpan2.fT)) { + return; + } + if (!way_roughly_equal(span.fOtherT, oSpan.fT) + || !way_roughly_equal(span2.fOtherT, oSpan2.fT) + || !way_roughly_equal(span2.fOtherT, oSpan.fOtherT) + || !way_roughly_equal(span.fOtherT, oSpan2.fOtherT)) { + return; + } + alignSpan(thisPt, span.fOtherT, other1, span2.fOtherT, + other2, &oSpan, alignedArray); + alignSpan(thisPt, span2.fOtherT, other2, span.fOtherT, + other1, &oSpan2, alignedArray); + break; + } + } + skipExactMatches: + ; + } + ++index; + } + } + debugValidate(); +} + +void SkOpSegment::alignSpan(const SkPoint& newPt, double newT, const SkOpSegment* other, + double otherT, const SkOpSegment* other2, SkOpSpan* oSpan, + SkTDArray<AlignedSpan>* alignedArray) { + AlignedSpan* aligned = alignedArray->append(); + aligned->fOldPt = oSpan->fPt; + aligned->fPt = newPt; + aligned->fOldT = oSpan->fT; + aligned->fT = newT; + aligned->fSegment = this; // OPTIMIZE: may be unused, can remove + aligned->fOther1 = other; + aligned->fOther2 = other2; + SkASSERT(SkDPoint::RoughlyEqual(oSpan->fPt, newPt)); + oSpan->fPt = newPt; +// SkASSERT(way_roughly_equal(oSpan->fT, newT)); + oSpan->fT = newT; +// SkASSERT(way_roughly_equal(oSpan->fOtherT, otherT)); + oSpan->fOtherT = otherT; +} + bool SkOpSegment::alignSpan(int index, double thisT, const SkPoint& thisPt) { bool aligned = false; SkOpSpan* span = &fTs[index]; @@ -877,6 +1030,156 @@ void SkOpSegment::alignSpanState(int start, int end) { } } +void SkOpSegment::blindCancel(const SkCoincidence& coincidence, SkOpSegment* other) { + bool binary = fOperand != other->fOperand; + int index = 0; + int last = this->count(); + do { + SkOpSpan& span = this->fTs[--last]; + if (span.fT != 1 && !span.fSmall) { + break; + } + span.fCoincident = true; + } while (true); + int oIndex = other->count(); + do { + SkOpSpan& oSpan = other->fTs[--oIndex]; + if (oSpan.fT != 1 && !oSpan.fSmall) { + break; + } + oSpan.fCoincident = true; + } while (true); + do { + SkOpSpan* test = &this->fTs[index]; + int baseWind = test->fWindValue; + int baseOpp = test->fOppValue; + int endIndex = index; + while (++endIndex <= last) { + SkOpSpan* endSpan = &this->fTs[endIndex]; + SkASSERT(endSpan->fT < 1); + if (endSpan->fWindValue != baseWind || endSpan->fOppValue != baseOpp) { + break; + } + endSpan->fCoincident = true; + } + SkOpSpan* oTest = &other->fTs[oIndex]; + int oBaseWind = oTest->fWindValue; + int oBaseOpp = oTest->fOppValue; + int oStartIndex = oIndex; + while (--oStartIndex >= 0) { + SkOpSpan* oStartSpan = &other->fTs[oStartIndex]; + if (oStartSpan->fWindValue != oBaseWind || oStartSpan->fOppValue != oBaseOpp) { + break; + } + oStartSpan->fCoincident = true; + } + bool decrement = baseWind && oBaseWind; + bool bigger = baseWind >= oBaseWind; + do { + SkASSERT(test->fT < 1); + if (decrement) { + if (binary && bigger) { + test->fOppValue--; + } else { + decrementSpan(test); + } + } + test->fCoincident = true; + test = &fTs[++index]; + } while (index < endIndex); + do { + SkASSERT(oTest->fT < 1); + if (decrement) { + if (binary && !bigger) { + oTest->fOppValue--; + } else { + other->decrementSpan(oTest); + } + } + oTest->fCoincident = true; + oTest = &other->fTs[--oIndex]; + } while (oIndex > oStartIndex); + } while (index <= last && oIndex >= 0); + SkASSERT(index > last); + SkASSERT(oIndex < 0); +} + +void SkOpSegment::blindCoincident(const SkCoincidence& coincidence, SkOpSegment* other) { + bool binary = fOperand != other->fOperand; + int index = 0; + int last = this->count(); + do { + SkOpSpan& span = this->fTs[--last]; + if (span.fT != 1 && !span.fSmall) { + break; + } + span.fCoincident = true; + } while (true); + int oIndex = 0; + int oLast = other->count(); + do { + SkOpSpan& oSpan = other->fTs[--oLast]; + if (oSpan.fT != 1 && !oSpan.fSmall) { + break; + } + oSpan.fCoincident = true; + } while (true); + do { + SkOpSpan* test = &this->fTs[index]; + int baseWind = test->fWindValue; + int baseOpp = test->fOppValue; + int endIndex = index; + SkOpSpan* endSpan; + while (++endIndex <= last) { + endSpan = &this->fTs[endIndex]; + SkASSERT(endSpan->fT < 1); + if (endSpan->fWindValue != baseWind || endSpan->fOppValue != baseOpp) { + break; + } + endSpan->fCoincident = true; + } + SkOpSpan* oTest = &other->fTs[oIndex]; + int oBaseWind = oTest->fWindValue; + int oBaseOpp = oTest->fOppValue; + int oEndIndex = oIndex; + SkOpSpan* oEndSpan; + while (++oEndIndex <= oLast) { + oEndSpan = &this->fTs[oEndIndex]; + SkASSERT(oEndSpan->fT < 1); + if (oEndSpan->fWindValue != oBaseWind || oEndSpan->fOppValue != oBaseOpp) { + break; + } + oEndSpan->fCoincident = true; + } + // consolidate the winding count even if done + if ((test->fWindValue || test->fOppValue) && (oTest->fWindValue || oTest->fOppValue)) { + if (!binary || test->fWindValue + oTest->fOppValue >= 0) { + bumpCoincidentBlind(binary, index, endIndex); + other->bumpCoincidentOBlind(oIndex, oEndIndex); + } else { + other->bumpCoincidentBlind(binary, oIndex, oEndIndex); + bumpCoincidentOBlind(index, endIndex); + } + } + index = endIndex; + oIndex = oEndIndex; + } while (index <= last && oIndex <= oLast); + SkASSERT(index > last); + SkASSERT(oIndex > oLast); +} + +void SkOpSegment::bumpCoincidentBlind(bool binary, int index, int endIndex) { + const SkOpSpan& oTest = fTs[index]; + int oWindValue = oTest.fWindValue; + int oOppValue = oTest.fOppValue; + if (binary) { + SkTSwap<int>(oWindValue, oOppValue); + } + do { + (void) bumpSpan(&fTs[index], oWindValue, oOppValue); + } while (++index < endIndex); +} + void SkOpSegment::bumpCoincidentThis(const SkOpSpan& oTest, bool binary, int* indexPtr, SkTArray<SkPoint, true>* outsideTs) { int index = *indexPtr; @@ -897,6 +1200,12 @@ void SkOpSegment::bumpCoincidentThis(const SkOpSpan& oTest, bool binary, int* in *indexPtr = index; } +void SkOpSegment::bumpCoincidentOBlind(int index, int endIndex) { + do { + zeroSpan(&fTs[index]); + } while (++index < endIndex); +} + // because of the order in which coincidences are resolved, this and other // may not have the same intermediate points. Compute the corresponding // intermediate T values (using this as the master, other as the follower) @@ -1025,13 +1334,13 @@ void SkOpSegment::addTCoincident(const SkPoint& startPt, const SkPoint& endPt, d int oPeekIndex = oIndex; bool success = true; SkOpSpan* oPeek; + int oCount = other->count(); do { oPeek = &other->fTs[oPeekIndex]; - if (oPeek->fT == 1) { + if (++oPeekIndex == oCount) { success = false; break; } - ++oPeekIndex; } while (endPt != oPeek->fPt); if (success) { // make sure the matching point completes the coincidence span @@ -1063,12 +1372,14 @@ void SkOpSegment::addTCoincident(const SkPoint& startPt, const SkPoint& endPt, d if (!other->done() && oOutsidePts.count()) { other->addCoinOutsides(oOutsidePts[0], endPt, this); } + setCoincidentRange(startPt, endPt, other); + other->setCoincidentRange(startPt, endPt, this); } // FIXME: this doesn't prevent the same span from being added twice // fix in caller, SkASSERT here? const SkOpSpan* SkOpSegment::addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, - const SkPoint& pt) { + const SkPoint& pt, const SkPoint& pt2) { int tCount = fTs.count(); for (int tIndex = 0; tIndex < tCount; ++tIndex) { const SkOpSpan& span = fTs[tIndex]; @@ -1089,24 +1400,23 @@ const SkOpSpan* SkOpSegment::addTPair(double t, SkOpSegment* other, double other __FUNCTION__, fID, t, other->fID, otherT); #endif int insertedAt = addT(other, pt, t); - int otherInsertedAt = other->addT(this, pt, otherT); + int otherInsertedAt = other->addT(this, pt2, otherT); addOtherT(insertedAt, otherT, otherInsertedAt); other->addOtherT(otherInsertedAt, t, insertedAt); matchWindingValue(insertedAt, t, borrowWind); other->matchWindingValue(otherInsertedAt, otherT, borrowWind); - return &span(insertedAt); + SkOpSpan& span = this->fTs[insertedAt]; + if (pt != pt2) { + span.fNear = true; + SkOpSpan& oSpan = other->fTs[otherInsertedAt]; + oSpan.fNear = true; + } + return &span; } -const SkOpAngle& SkOpSegment::angle(int index) const { - SkASSERT(index >= 0); - int count = fAngles.count(); - if (index < count) { - return fAngles[index]; - } - index -= count; - count = fSingletonAngles.count(); - SkASSERT(index < count); - return fSingletonAngles[index]; +const SkOpSpan* SkOpSegment::addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, + const SkPoint& pt) { + return addTPair(t, other, otherT, borrowWind, pt, pt); } bool SkOpSegment::betweenPoints(double midT, const SkPoint& pt1, const SkPoint& pt2) const { @@ -1138,9 +1448,7 @@ bool SkOpSegment::calcAngles() { const SkOpSpan* span = &fTs[0]; if (firstSpan->fT == 0 || span->fTiny || span->fOtherT != 1 || span->fOther->multipleEnds()) { index = findStartSpan(0); // curve start intersects - if (index < 0) { - return false; - } + SkASSERT(index > 0); if (activePrior >= 0) { addStartSpan(index); } @@ -1150,9 +1458,7 @@ bool SkOpSegment::calcAngles() { span = &fTs[endIndex - 1]; if ((addEnd = span->fT == 1 || span->fTiny)) { // if curve end intersects endIndex = findEndSpan(endIndex); - if (endIndex < 0) { - return false; - } + SkASSERT(endIndex > 0); } else { addEnd = fTs[endIndex].fOtherT != 0 || fTs[endIndex].fOther->multipleStarts(); } @@ -1174,24 +1480,19 @@ bool SkOpSegment::calcAngles() { return false; } } while (true); - int angleIndex = fAngles.count(); - int priorAngleIndex; + SkOpAngle* angle = NULL; + SkOpAngle* priorAngle; if (activePrior >= 0) { int pActive = firstActive(prior); SkASSERT(pActive < start); - fAngles.push_back().set(this, start, pActive); - priorAngleIndex = angleIndex++; - #if DEBUG_ANGLE - fAngles.back().setID(priorAngleIndex); - #endif + priorAngle = &fAngles.push_back(); + priorAngle->set(this, start, pActive); } int active = checkSetAngle(start); if (active >= 0) { SkASSERT(active < index); - fAngles.push_back().set(this, active, index); - #if DEBUG_ANGLE - fAngles.back().setID(angleIndex); - #endif + angle = &fAngles.push_back(); + angle->set(this, active, index); } #if DEBUG_ANGLE debugCheckPointsEqualish(start, index); @@ -1199,20 +1500,20 @@ bool SkOpSegment::calcAngles() { prior = start; do { const SkOpSpan* startSpan = &fTs[start - 1]; - if (!startSpan->fSmall || startSpan->fFromAngleIndex >= 0 - || startSpan->fToAngleIndex >= 0) { + if (!startSpan->fSmall || isCanceled(start - 1) || startSpan->fFromAngle + || startSpan->fToAngle) { break; } --start; } while (start > 0); do { if (activePrior >= 0) { - SkASSERT(fTs[start].fFromAngleIndex == -1); - fTs[start].fFromAngleIndex = priorAngleIndex; + SkASSERT(fTs[start].fFromAngle == NULL); + fTs[start].fFromAngle = priorAngle; } if (active >= 0) { - SkASSERT(fTs[start].fToAngleIndex == -1); - fTs[start].fToAngleIndex = angleIndex; + SkASSERT(fTs[start].fToAngle == NULL); + fTs[start].fToAngle = angle; } } while (++start < index); activePrior = active; @@ -1231,7 +1532,7 @@ int SkOpSegment::checkSetAngle(int tIndex) const { return isCanceled(tIndex) ? -1 : tIndex; } -// at this point, the span is already ordered, or unorderable, or unsortable +// at this point, the span is already ordered, or unorderable int SkOpSegment::computeSum(int startIndex, int endIndex, SkOpAngle::IncludeType includeType) { SkASSERT(includeType != SkOpAngle::kUnaryXor); SkOpAngle* firstAngle = spanToAngle(endIndex, startIndex); @@ -1242,50 +1543,61 @@ int SkOpSegment::computeSum(int startIndex, int endIndex, SkOpAngle::IncludeType // or if no adjacent angles are orderable, // or if adjacent orderable angles have no computed winding, // there's nothing to do - // if two orderable angles are adjacent, and one has winding computed, transfer to the other + // if two orderable angles are adjacent, and both are next to orderable angles, + // and one has winding computed, transfer to the other SkOpAngle* baseAngle = NULL; bool tryReverse = false; // look for counterclockwise transfers - SkOpAngle* angle = firstAngle; + SkOpAngle* angle = firstAngle->previous(); + SkOpAngle* next = angle->next(); + firstAngle = next; do { - int testWinding = angle->segment()->windSum(angle); - if (SK_MinS32 != testWinding && !angle->unorderable()) { - baseAngle = angle; + SkOpAngle* prior = angle; + angle = next; + next = angle->next(); + SkASSERT(prior->next() == angle); + SkASSERT(angle->next() == next); + if (prior->unorderable() || angle->unorderable() || next->unorderable()) { + baseAngle = NULL; continue; } - if (angle->unorderable()) { - baseAngle = NULL; + int testWinding = angle->segment()->windSum(angle); + if (SK_MinS32 != testWinding) { + baseAngle = angle; tryReverse = true; continue; } if (baseAngle) { ComputeOneSum(baseAngle, angle, includeType); baseAngle = SK_MinS32 != angle->segment()->windSum(angle) ? angle : NULL; - tryReverse |= !baseAngle; } - } while ((angle = angle->next()) != firstAngle); - if (baseAngle && SK_MinS32 == firstAngle->segment()->windSum(angle)) { + } while (next != firstAngle); + if (baseAngle && SK_MinS32 == firstAngle->segment()->windSum(firstAngle)) { firstAngle = baseAngle; tryReverse = true; } if (tryReverse) { baseAngle = NULL; - angle = firstAngle; + SkOpAngle* prior = firstAngle; do { + angle = prior; + prior = angle->previous(); + SkASSERT(prior->next() == angle); + next = angle->next(); + if (prior->unorderable() || angle->unorderable() || next->unorderable()) { + baseAngle = NULL; + continue; + } int testWinding = angle->segment()->windSum(angle); if (SK_MinS32 != testWinding) { baseAngle = angle; continue; } - if (angle->unorderable()) { - baseAngle = NULL; - continue; - } if (baseAngle) { ComputeOneSumReverse(baseAngle, angle, includeType); baseAngle = SK_MinS32 != angle->segment()->windSum(angle) ? angle : NULL; } - } while ((angle = angle->previous()) != firstAngle); + } while (prior != firstAngle); } int minIndex = SkMin32(startIndex, endIndex); return windSum(minIndex); @@ -1362,6 +1674,31 @@ bool SkOpSegment::containsPt(const SkPoint& pt, int index, int endIndex) const { return false; } +bool SkOpSegment::containsT(double t, const SkOpSegment* other, double otherT) const { + int count = this->count(); + for (int index = 0; index < count; ++index) { + const SkOpSpan& span = fTs[index]; + if (t < span.fT) { + return false; + } + if (t == span.fT) { + if (other != span.fOther) { + continue; + } + if (other->fVerb != SkPath::kCubic_Verb) { + return true; + } + if (!other->fLoop) { + return true; + } + double otherMidT = (otherT + span.fOtherT) / 2; + SkPoint otherPt = other->ptAtT(otherMidT); + return SkDPoint::ApproximatelyEqual(span.fPt, otherPt); + } + } + return false; +} + int SkOpSegment::crossedSpanY(const SkPoint& basePt, SkScalar* bestY, double* hitT, bool* hitSomething, double mid, bool opp, bool current) const { SkScalar bottom = fBounds.fBottom; @@ -1542,6 +1879,58 @@ bool SkOpSegment::calcLoopSpanCount(const SkOpSpan& thisSpan, int* smallCounts) return true; } +double SkOpSegment::calcMissingTEnd(const SkOpSegment* ref, double loEnd, double min, double max, + double hiEnd, const SkOpSegment* other, int thisStart) { + if (max >= hiEnd) { + return -1; + } + int end = findOtherT(hiEnd, ref); + if (end < 0) { + return -1; + } + double tHi = span(end).fT; + double tLo, refLo; + if (thisStart >= 0) { + tLo = span(thisStart).fT; + refLo = min; + } else { + int start1 = findOtherT(loEnd, ref); + SkASSERT(start1 >= 0); + tLo = span(start1).fT; + refLo = loEnd; + } + double missingT = (max - refLo) / (hiEnd - refLo); + missingT = tLo + missingT * (tHi - tLo); + return missingT; +} + +double SkOpSegment::calcMissingTStart(const SkOpSegment* ref, double loEnd, double min, double max, + double hiEnd, const SkOpSegment* other, int thisEnd) { + if (min <= loEnd) { + return -1; + } + int start = findOtherT(loEnd, ref); + if (start < 0) { + return -1; + } + double tLo = span(start).fT; + double tHi, refHi; + if (thisEnd >= 0) { + tHi = span(thisEnd).fT; + refHi = max; + } else { + int end1 = findOtherT(hiEnd, ref); + if (end1 < 0) { + return -1; + } + tHi = span(end1).fT; + refHi = hiEnd; + } + double missingT = (min - loEnd) / (refHi - loEnd); + missingT = tLo + missingT * (tHi - tLo); + return missingT; +} + // see if spans with two or more intersections have the same number on the other end void SkOpSegment::checkDuplicates() { debugValidate(); @@ -1561,6 +1950,9 @@ void SkOpSegment::checkDuplicates() { } do { const SkOpSpan* thisSpan = &fTs[index]; + if (thisSpan->fNear) { + continue; + } SkOpSegment* other = thisSpan->fOther; int oIndex = thisSpan->fOtherIndex; int oStart = other->nextExactSpan(oIndex, -1) + 1; @@ -1602,6 +1994,33 @@ void SkOpSegment::checkDuplicates() { if (missing.fSegment == missing.fOther) { continue; } +#if 0 // FIXME: this eliminates spurious data from skpwww_argus_presse_fr_41 but breaks + // skpwww_fashionscandal_com_94 -- calcAngles complains, but I don't understand why + if (missing.fSegment->containsT(missing.fT, missing.fOther, missing.fOtherT)) { +#if DEBUG_DUPLICATES + SkDebugf("skip 1 id=%d t=%1.9g other=%d otherT=%1.9g\n", missing.fSegment->fID, + missing.fT, missing.fOther->fID, missing.fOtherT); +#endif + continue; + } + if (missing.fOther->containsT(missing.fOtherT, missing.fSegment, missing.fT)) { +#if DEBUG_DUPLICATES + SkDebugf("skip 2 id=%d t=%1.9g other=%d otherT=%1.9g\n", missing.fOther->fID, + missing.fOtherT, missing.fSegment->fID, missing.fT); +#endif + continue; + } +#endif + // skip if adding would insert point into an existing coincindent span + if (missing.fSegment->inCoincidentSpan(missing.fT, missingOther) + && missingOther->inCoincidentSpan(missing.fOtherT, this)) { + continue; + } + // skip if the created coincident spans are small + if (missing.fSegment->coincidentSmall(missing.fPt, missing.fT, missingOther) + && missingOther->coincidentSmall(missing.fPt, missing.fOtherT, missing.fSegment)) { + continue; + } const SkOpSpan* added = missing.fSegment->addTPair(missing.fT, missingOther, missing.fOtherT, false, missing.fPt); if (added && added->fSmall) { @@ -1777,8 +2196,10 @@ void SkOpSegment::checkMultiples() { continue; } // force the duplicates to agree on t and pt if not on the end - double thisT = fTs[index].fT; - const SkPoint& thisPt = fTs[index].fPt; + SkOpSpan& span = fTs[index]; + double thisT = span.fT; + const SkPoint& thisPt = span.fPt; + span.fMultiple = true; bool aligned = false; while (++index < end) { aligned |= alignSpan(index, thisT, thisPt); @@ -1786,6 +2207,7 @@ void SkOpSegment::checkMultiples() { if (aligned) { alignSpanState(start, end); } + fMultiples = true; } debugValidate(); } @@ -2146,6 +2568,30 @@ void SkOpSegment::checkTiny() { } } +bool SkOpSegment::coincidentSmall(const SkPoint& pt, double t, const SkOpSegment* other) const { + int count = this->count(); + for (int index = 0; index < count; ++index) { + const SkOpSpan& span = this->span(index); + if (span.fOther != other) { + continue; + } + if (span.fPt == pt) { + continue; + } + if (!AlmostEqualUlps(span.fPt, pt)) { + continue; + } + if (fVerb != SkPath::kCubic_Verb) { + return true; + } + double tInterval = t - span.fT; + double tMid = t - tInterval / 2; + SkDPoint midPt = dcubic_xy_at_t(fPts, tMid); + return midPt.approximatelyEqual(xyAtT(t)); + } + return false; +} + bool SkOpSegment::findCoincidentMatch(const SkOpSpan* span, const SkOpSegment* other, int oStart, int oEnd, int step, SkPoint* startPt, SkPoint* endPt, double* endT) const { SkASSERT(span->fT == 0 || span->fT == 1); @@ -2235,12 +2681,11 @@ SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart SkASSERT(startIndex != endIndex); SkDEBUGCODE(const int count = fTs.count()); SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0); - const int step = SkSign32(endIndex - startIndex); - const int end = nextExactSpan(startIndex, step); - SkASSERT(end >= 0); - SkOpSpan* endSpan = &fTs[end]; - SkOpSegment* other; - if (isSimple(end)) { + int step = SkSign32(endIndex - startIndex); + *nextStart = startIndex; + SkOpSegment* other = isSimple(nextStart, &step); + if (other) + { // mark the smaller of startIndex, endIndex done, and all adjacent // spans with the same T value (but not 'other' spans) #if DEBUG_WINDING @@ -2251,8 +2696,6 @@ SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart return NULL; } markDoneBinary(min); - other = endSpan->fOther; - *nextStart = endSpan->fOtherIndex; double startT = other->fTs[*nextStart].fT; *nextEnd = *nextStart; do { @@ -2265,6 +2708,8 @@ SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart } return other; } + const int end = nextExactSpan(startIndex, step); + SkASSERT(end >= 0); SkASSERT(startIndex - endIndex != 0); SkASSERT((startIndex - endIndex < 0) ^ (step < 0)); // more than one viable candidate -- measure angles to find best @@ -2325,7 +2770,7 @@ SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart continue; } if (!activeAngle) { - nextSegment->markAndChaseDoneBinary(nextAngle->start(), nextAngle->end()); + (void) nextSegment->markAndChaseDoneBinary(nextAngle->start(), nextAngle->end()); } SkOpSpan* last = nextAngle->lastMarked(); if (last) { @@ -2365,12 +2810,11 @@ SkOpSegment* SkOpSegment::findNextWinding(SkTDArray<SkOpSpan*>* chase, int* next SkASSERT(startIndex != endIndex); SkDEBUGCODE(const int count = fTs.count()); SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0); - const int step = SkSign32(endIndex - startIndex); - const int end = nextExactSpan(startIndex, step); - SkASSERT(end >= 0); - SkOpSpan* endSpan = &fTs[end]; - SkOpSegment* other; - if (isSimple(end)) { + int step = SkSign32(endIndex - startIndex); + *nextStart = startIndex; + SkOpSegment* other = isSimple(nextStart, &step); + if (other) + { // mark the smaller of startIndex, endIndex done, and all adjacent // spans with the same T value (but not 'other' spans) #if DEBUG_WINDING @@ -2381,8 +2825,6 @@ SkOpSegment* SkOpSegment::findNextWinding(SkTDArray<SkOpSpan*>* chase, int* next return NULL; } markDoneUnary(min); - other = endSpan->fOther; - *nextStart = endSpan->fOtherIndex; double startT = other->fTs[*nextStart].fT; *nextEnd = *nextStart; do { @@ -2395,6 +2837,8 @@ SkOpSegment* SkOpSegment::findNextWinding(SkTDArray<SkOpSpan*>* chase, int* next } return other; } + const int end = nextExactSpan(startIndex, step); + SkASSERT(end >= 0); SkASSERT(startIndex - endIndex != 0); SkASSERT((startIndex - endIndex < 0) ^ (step < 0)); // more than one viable candidate -- measure angles to find best @@ -2474,13 +2918,12 @@ SkOpSegment* SkOpSegment::findNextXor(int* nextStart, int* nextEnd, bool* unsort SkDEBUGCODE(int count = fTs.count()); SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0); int step = SkSign32(endIndex - startIndex); - int end = nextExactSpan(startIndex, step); - SkASSERT(end >= 0); - SkOpSpan* endSpan = &fTs[end]; - SkOpSegment* other; // Detect cases where all the ends canceled out (e.g., -// there is no angle) and therefore there's only one valid connection - if (isSimple(end) || !spanToAngle(end, startIndex)) { +// there is no angle) and therefore there's only one valid connection + *nextStart = startIndex; + SkOpSegment* other = isSimple(nextStart, &step); + if (other) + { #if DEBUG_WINDING SkDebugf("%s simple\n", __FUNCTION__); #endif @@ -2489,8 +2932,6 @@ SkOpSegment* SkOpSegment::findNextXor(int* nextStart, int* nextEnd, bool* unsort return NULL; } markDone(min, 1); - other = endSpan->fOther; - *nextStart = endSpan->fOtherIndex; double startT = other->fTs[*nextStart].fT; // FIXME: I don't know why the logic here is difference from the winding case SkDEBUGCODE(bool firstLoop = true;) @@ -2517,6 +2958,8 @@ SkOpSegment* SkOpSegment::findNextXor(int* nextStart, int* nextEnd, bool* unsort SkASSERT(startIndex - endIndex != 0); SkASSERT((startIndex - endIndex < 0) ^ (step < 0)); // parallel block above with presorted version + int end = nextExactSpan(startIndex, step); + SkASSERT(end >= 0); SkOpAngle* angle = spanToAngle(end, startIndex); SkASSERT(angle); #if DEBUG_SORT @@ -2562,24 +3005,12 @@ int SkOpSegment::findStartSpan(int startIndex) const { const SkOpSpan* span = &fTs[index]; const SkPoint& firstPt = span->fPt; double firstT = span->fT; + const SkOpSpan* prior; do { - if (index > 0) { - if (span->fT != firstT) { - break; - } - if (!SkDPoint::ApproximatelyEqual(firstPt, fTs[index].fPt)) { - return -1; - } - } - ++index; - if (span->fTiny) { - if (!SkDPoint::ApproximatelyEqual(firstPt, fTs[index].fPt)) { - return -1; - } - firstT = fTs[index++].fT; - } - span = &fTs[index]; - } while (true); + prior = span; + span = &fTs[++index]; + } while (SkDPoint::ApproximatelyEqual(span->fPt, firstPt) + && (span->fT == firstT || prior->fTiny)); return index; } @@ -2595,6 +3026,17 @@ int SkOpSegment::findExactT(double t, const SkOpSegment* match) const { return -1; } +int SkOpSegment::findOtherT(double t, const SkOpSegment* match) const { + int count = this->count(); + for (int index = 0; index < count; ++index) { + const SkOpSpan& span = fTs[index]; + if (span.fOtherT == t && span.fOther == match) { + return index; + } + } + return -1; +} + int SkOpSegment::findT(double t, const SkPoint& pt, const SkOpSegment* match) const { int count = this->count(); for (int index = 0; index < count; ++index) { @@ -2647,7 +3089,7 @@ SkOpSegment* SkOpSegment::findTop(int* tIndexPtr, int* endIndexPtr, bool* unsort SkASSERT(firstT - end != 0); SkOpAngle* markAngle = spanToAngle(firstT, end); if (!markAngle) { - markAngle = addSingletonAngles(firstT, step); + markAngle = addSingletonAngles(step); } markAngle->markStops(); const SkOpAngle* baseAngle = markAngle->findFirst(); @@ -2754,13 +3196,26 @@ void SkOpSegment::fixOtherTIndex() { } } +bool SkOpSegment::inCoincidentSpan(double t, const SkOpSegment* other) const { + int foundEnds = 0; + int count = this->count(); + for (int index = 0; index < count; ++index) { + const SkOpSpan& span = this->span(index); + if (span.fCoincident) { + foundEnds |= (span.fOther == other) << ((t > span.fT) + (t >= span.fT)); + } + } + SkASSERT(foundEnds != 7); + return foundEnds == 0x3 || foundEnds == 0x5 || foundEnds == 0x6; // two bits set +} + void SkOpSegment::init(const SkPoint pts[], SkPath::Verb verb, bool operand, bool evenOdd) { fDoneSpans = 0; fOperand = operand; fXor = evenOdd; fPts = pts; fVerb = verb; - fLoop = fSmall = fTiny = false; + fLoop = fMultiples = fSmall = fTiny = false; } void SkOpSegment::initWinding(int start, int end, SkOpAngle::IncludeType angleIncludeType) { @@ -2793,16 +3248,13 @@ void SkOpSegment::initWinding(int start, int end, double tHit, int winding, SkSc SkASSERT(dx); int windVal = windValue(SkMin32(start, end)); #if DEBUG_WINDING_AT_T - SkDebugf("%s oldWinding=%d hitDx=%c dx=%c windVal=%d", __FUNCTION__, winding, + SkDebugf("%s id=%d oldWinding=%d hitDx=%c dx=%c windVal=%d", __FUNCTION__, debugID(), winding, hitDx ? hitDx > 0 ? '+' : '-' : '0', dx > 0 ? '+' : '-', windVal); #endif int sideWind = winding + (dx < 0 ? windVal : -windVal); if (abs(winding) < abs(sideWind)) { winding = sideWind; } -#if DEBUG_WINDING_AT_T - SkDebugf(" winding=%d\n", winding); -#endif SkDEBUGCODE(int oppLocal = oppSign(start, end)); SkASSERT(hitOppDx || !oppWind || !oppLocal); int oppWindVal = oppValue(SkMin32(start, end)); @@ -2814,6 +3266,9 @@ void SkOpSegment::initWinding(int start, int end, double tHit, int winding, SkSc oppWind = oppSideWind; } } +#if DEBUG_WINDING_AT_T + SkDebugf(" winding=%d oppWind=%d\n", winding, oppWind); +#endif (void) markAndChaseWinding(start, end, winding, oppWind); // OPTIMIZATION: the reverse mark and chase could skip the first marking (void) markAndChaseWinding(end, start, winding, oppWind); @@ -2824,12 +3279,12 @@ bool SkOpSegment::inLoop(const SkOpAngle* baseAngle, int spanCount, int* indexPt return false; } int index = *indexPtr; - int froIndex = fTs[index].fFromAngleIndex; - int toIndex = fTs[index].fToAngleIndex; + SkOpAngle* from = fTs[index].fFromAngle; + SkOpAngle* to = fTs[index].fToAngle; while (++index < spanCount) { - int nextFro = fTs[index].fFromAngleIndex; - int nextTo = fTs[index].fToAngleIndex; - if (froIndex != nextFro || toIndex != nextTo) { + SkOpAngle* nextFrom = fTs[index].fFromAngle; + SkOpAngle* nextTo = fTs[index].fToAngle; + if (from != nextFrom || to != nextTo) { break; } } @@ -2850,26 +3305,9 @@ bool SkOpSegment::isMissing(double startT, const SkPoint& pt) const { return true; } -bool SkOpSegment::isSimple(int end) const { -#if 1 - int count = fTs.count(); - if (count == 2) { - return true; - } - double t = fTs[end].fT; - if (approximately_less_than_zero(t)) { - return !approximately_less_than_zero(fTs[1].fT); - } - if (approximately_greater_than_one(t)) { - return !approximately_greater_than_one(fTs[count - 2].fT); - } - return false; -#else - // OPTIMIZE: code could be rejiggered to use this simpler test. To make this work, a little - // more logic is required to ignore the dangling canceled out spans - const SkOpSpan& span = fTs[end]; - return span.fFromAngleIndex < 0 && span.fToAngleIndex < 0; -#endif + +SkOpSegment* SkOpSegment::isSimple(int* end, int* step) { + return nextChase(end, step, NULL, NULL); } bool SkOpSegment::isTiny(const SkOpAngle* angle) const { @@ -2928,11 +3366,12 @@ SkOpSpan* SkOpSegment::markAndChaseDoneBinary(int index, int endIndex) { int step = SkSign32(endIndex - index); int min = SkMin32(index, endIndex); markDoneBinary(min); - SkOpSpan* last; + SkOpSpan* last = NULL; SkOpSegment* other = this; - while ((other = other->nextChase(&index, step, &min, &last))) { + while ((other = other->nextChase(&index, &step, &min, &last))) { if (other->done()) { - return NULL; + SkASSERT(!last); + break; } other->markDoneBinary(min); } @@ -2943,11 +3382,12 @@ SkOpSpan* SkOpSegment::markAndChaseDoneUnary(int index, int endIndex) { int step = SkSign32(endIndex - index); int min = SkMin32(index, endIndex); markDoneUnary(min); - SkOpSpan* last; + SkOpSpan* last = NULL; SkOpSegment* other = this; - while ((other = other->nextChase(&index, step, &min, &last))) { + while ((other = other->nextChase(&index, &step, &min, &last))) { if (other->done()) { - return NULL; + SkASSERT(!last); + break; } other->markDoneUnary(min); } @@ -2960,12 +3400,13 @@ SkOpSpan* SkOpSegment::markAndChaseWinding(const SkOpAngle* angle, int winding) int step = SkSign32(endIndex - index); int min = SkMin32(index, endIndex); markWinding(min, winding); - SkOpSpan* last; + SkOpSpan* last = NULL; SkOpSegment* other = this; - while ((other = other->nextChase(&index, step, &min, &last))) { + while ((other = other->nextChase(&index, &step, &min, &last))) { if (other->fTs[min].fWindSum != SK_MinS32) { SkASSERT(other->fTs[min].fWindSum == winding); - return NULL; + SkASSERT(!last); + break; } other->markWinding(min, winding); } @@ -2976,12 +3417,13 @@ SkOpSpan* SkOpSegment::markAndChaseWinding(int index, int endIndex, int winding) int min = SkMin32(index, endIndex); int step = SkSign32(endIndex - index); markWinding(min, winding); - SkOpSpan* last; + SkOpSpan* last = NULL; SkOpSegment* other = this; - while ((other = other->nextChase(&index, step, &min, &last))) { + while ((other = other->nextChase(&index, &step, &min, &last))) { if (other->fTs[min].fWindSum != SK_MinS32) { SkASSERT(other->fTs[min].fWindSum == winding || other->fTs[min].fLoop); - return NULL; + SkASSERT(!last); + break; } other->markWinding(min, winding); } @@ -2992,14 +3434,32 @@ SkOpSpan* SkOpSegment::markAndChaseWinding(int index, int endIndex, int winding, int min = SkMin32(index, endIndex); int step = SkSign32(endIndex - index); markWinding(min, winding, oppWinding); - SkOpSpan* last; + SkOpSpan* last = NULL; SkOpSegment* other = this; - while ((other = other->nextChase(&index, step, &min, &last))) { + while ((other = other->nextChase(&index, &step, &min, &last))) { if (other->fTs[min].fWindSum != SK_MinS32) { - SkASSERT(other->fTs[min].fWindSum == winding || other->fTs[min].fLoop); - return NULL; +#if SK_DEBUG + if (!other->fTs[min].fLoop) { + if (fOperand == other->fOperand) { +// FIXME: this is probably a bug -- rects4 asserts here +// SkASSERT(other->fTs[min].fWindSum == winding); +// FIXME: this is probably a bug -- rects3 asserts here +// SkASSERT(other->fTs[min].fOppSum == oppWinding); + } else { + SkASSERT(other->fTs[min].fWindSum == oppWinding); +// FIXME: this is probably a bug -- skpwww_joomla_org_23 asserts here +// SkASSERT(other->fTs[min].fOppSum == winding); + } + } + SkASSERT(!last); +#endif + break; + } + if (fOperand == other->fOperand) { + other->markWinding(min, winding, oppWinding); + } else { + other->markWinding(min, oppWinding, winding); } - other->markWinding(min, winding, oppWinding); } return last; } @@ -3316,15 +3776,6 @@ void SkOpSegment::matchWindingValue(int tIndex, double t, bool borrowWind) { } } -// return span if when chasing, two or more radiating spans are not done -// OPTIMIZATION: ? multiple spans is detected when there is only one valid -// candidate and the remaining spans have windValue == 0 (canceled by -// coincidence). The coincident edges could either be removed altogether, -// or this code could be more complicated in detecting this case. Worth it? -bool SkOpSegment::multipleSpans(int end) const { - return end > 0 && end < fTs.count() - 1; -} - bool SkOpSegment::nextCandidate(int* start, int* end) const { while (fTs[*end].fDone) { if (fTs[*end].fT == 1) { @@ -3337,27 +3788,67 @@ bool SkOpSegment::nextCandidate(int* start, int* end) const { return true; } -SkOpSegment* SkOpSegment::nextChase(int* index, const int step, int* min, SkOpSpan** last) { - int end = nextExactSpan(*index, step); +static SkOpSegment* set_last(SkOpSpan** last, SkOpSpan* endSpan) { + if (last && !endSpan->fSmall) { + *last = endSpan; + } + return NULL; +} + +SkOpSegment* SkOpSegment::nextChase(int* indexPtr, int* stepPtr, int* minPtr, SkOpSpan** last) { + int origIndex = *indexPtr; + int step = *stepPtr; + int end = nextExactSpan(origIndex, step); SkASSERT(end >= 0); - if (fTs[end].fSmall) { - *last = NULL; - return NULL; + SkOpSpan& endSpan = fTs[end]; + SkOpAngle* angle = step > 0 ? endSpan.fFromAngle : endSpan.fToAngle; + int foundIndex; + int otherEnd; + SkOpSegment* other; + if (angle == NULL) { + if (endSpan.fT != 0 && endSpan.fT != 1) { + return NULL; + } + other = endSpan.fOther; + foundIndex = endSpan.fOtherIndex; + otherEnd = other->nextExactSpan(foundIndex, step); + } else { + int loopCount = angle->loopCount(); + if (loopCount > 2) { + return set_last(last, &endSpan); + } + const SkOpAngle* next = angle->next(); + if (angle->sign() != next->sign()) { +#if DEBUG_WINDING + SkDebugf("%s mismatched signs\n", __FUNCTION__); +#endif + // return set_last(last, &endSpan); + } + other = next->segment(); + foundIndex = end = next->start(); + otherEnd = next->end(); } - if (multipleSpans(end)) { - *last = &fTs[end]; - return NULL; + int foundStep = foundIndex < otherEnd ? 1 : -1; + if (*stepPtr != foundStep) { + return set_last(last, &endSpan); } - const SkOpSpan& endSpan = fTs[end]; - SkOpSegment* other = endSpan.fOther; - *index = endSpan.fOtherIndex; - SkASSERT(*index >= 0); - int otherEnd = other->nextExactSpan(*index, step); + SkASSERT(*indexPtr >= 0); SkASSERT(otherEnd >= 0); - *min = SkMin32(*index, otherEnd); - if (other->fTs[*min].fSmall) { - *last = NULL; - return NULL; +#if 1 + int origMin = origIndex + (step < 0 ? step : 0); + const SkOpSpan& orig = this->span(origMin); +#endif + int foundMin = SkMin32(foundIndex, otherEnd); +#if 1 + const SkOpSpan& found = other->span(foundMin); + if (found.fWindValue != orig.fWindValue || found.fOppValue != orig.fOppValue) { + return set_last(last, &endSpan); + } +#endif + *indexPtr = foundIndex; + *stepPtr = foundStep; + if (minPtr) { + *minPtr = foundMin; } return other; } @@ -3414,6 +3905,27 @@ int SkOpSegment::nextExactSpan(int from, int step) const { return -1; } +void SkOpSegment::pinT(const SkPoint& pt, double* t) { + if (pt == fPts[0]) { + *t = 0; + } + int count = SkPathOpsVerbToPoints(fVerb); + if (pt == fPts[count]) { + *t = 1; + } +} + +void SkOpSegment::setCoincidentRange(const SkPoint& startPt, const SkPoint& endPt, + SkOpSegment* other) { + int count = this->count(); + for (int index = 0; index < count; ++index) { + SkOpSpan &span = fTs[index]; + if ((startPt == span.fPt || endPt == span.fPt) && other == span.fOther) { + span.fCoincident = true; + } + } +} + void SkOpSegment::setUpWindings(int index, int endIndex, int* sumMiWinding, int* sumSuWinding, int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding) { int deltaSum = spanSign(index, endIndex); @@ -3452,15 +3964,15 @@ void SkOpSegment::sortAngles() { } int index = 0; do { - int fromIndex = fTs[index].fFromAngleIndex; - int toIndex = fTs[index].fToAngleIndex; - if (fromIndex < 0 && toIndex < 0) { + SkOpAngle* fromAngle = fTs[index].fFromAngle; + SkOpAngle* toAngle = fTs[index].fToAngle; + if (!fromAngle && !toAngle) { index += 1; continue; } SkOpAngle* baseAngle = NULL; - if (fromIndex >= 0) { - baseAngle = &this->angle(fromIndex); + if (fromAngle) { + baseAngle = fromAngle; if (inLoop(baseAngle, spanCount, &index)) { continue; } @@ -3468,8 +3980,7 @@ void SkOpSegment::sortAngles() { #if DEBUG_ANGLE bool wroteAfterHeader = false; #endif - if (toIndex >= 0) { - SkOpAngle* toAngle = &this->angle(toIndex); + if (toAngle) { if (!baseAngle) { baseAngle = toAngle; if (inLoop(baseAngle, spanCount, &index)) { @@ -3486,14 +3997,14 @@ void SkOpSegment::sortAngles() { baseAngle->insert(toAngle); } } - int nextFrom, nextTo; + SkOpAngle* nextFrom, * nextTo; int firstIndex = index; do { SkOpSpan& span = fTs[index]; SkOpSegment* other = span.fOther; SkOpSpan& oSpan = other->fTs[span.fOtherIndex]; - int otherAngleIndex = oSpan.fFromAngleIndex; - if (otherAngleIndex >= 0) { + SkOpAngle* oAngle = oSpan.fFromAngle; + if (oAngle) { #if DEBUG_ANGLE if (!wroteAfterHeader) { SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), fTs[index].fT, @@ -3501,13 +4012,12 @@ void SkOpSegment::sortAngles() { wroteAfterHeader = true; } #endif - SkOpAngle* oAngle = &other->angle(otherAngleIndex); if (!oAngle->loopContains(*baseAngle)) { baseAngle->insert(oAngle); } } - otherAngleIndex = oSpan.fToAngleIndex; - if (otherAngleIndex >= 0) { + oAngle = oSpan.fToAngle; + if (oAngle) { #if DEBUG_ANGLE if (!wroteAfterHeader) { SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), fTs[index].fT, @@ -3515,7 +4025,6 @@ void SkOpSegment::sortAngles() { wroteAfterHeader = true; } #endif - SkOpAngle* oAngle = &other->angle(otherAngleIndex); if (!oAngle->loopContains(*baseAngle)) { baseAngle->insert(oAngle); } @@ -3523,20 +4032,20 @@ void SkOpSegment::sortAngles() { if (++index == spanCount) { break; } - nextFrom = fTs[index].fFromAngleIndex; - nextTo = fTs[index].fToAngleIndex; - } while (fromIndex == nextFrom && toIndex == nextTo); + nextFrom = fTs[index].fFromAngle; + nextTo = fTs[index].fToAngle; + } while (fromAngle == nextFrom && toAngle == nextTo); if (baseAngle && baseAngle->loopCount() == 1) { index = firstIndex; do { SkOpSpan& span = fTs[index]; - span.fFromAngleIndex = span.fToAngleIndex = -1; + span.fFromAngle = span.fToAngle = NULL; if (++index == spanCount) { break; } - nextFrom = fTs[index].fFromAngleIndex; - nextTo = fTs[index].fToAngleIndex; - } while (fromIndex == nextFrom && toIndex == nextTo); + nextFrom = fTs[index].fFromAngle; + nextTo = fTs[index].fToAngle; + } while (fromAngle == nextFrom && toAngle == nextTo); baseAngle = NULL; } #if DEBUG_SORT @@ -3749,7 +4258,8 @@ int SkOpSegment::windingAtT(double tHit, int tIndex, bool crossOpp, SkScalar* dx SkASSERT(winding != SK_MinS32); int windVal = crossOpp ? oppValue(tIndex) : windValue(tIndex); #if DEBUG_WINDING_AT_T - SkDebugf("%s oldWinding=%d windValue=%d", __FUNCTION__, winding, windVal); + SkDebugf("%s id=%d opp=%d tHit=%1.9g t=%1.9g oldWinding=%d windValue=%d", __FUNCTION__, + debugID(), crossOpp, tHit, t(tIndex), winding, windVal); #endif // see if a + change in T results in a +/- change in X (compute x'(T)) *dx = (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, tHit).fX; diff --git a/src/pathops/SkOpSegment.h b/src/pathops/SkOpSegment.h index 1eaef69847..90ed553bd3 100644 --- a/src/pathops/SkOpSegment.h +++ b/src/pathops/SkOpSegment.h @@ -18,6 +18,7 @@ #include "SkThread.h" #endif +struct SkCoincidence; class SkPathWriter; class SkOpSegment { @@ -32,11 +33,15 @@ public: return fBounds.fTop < rh.fBounds.fTop; } - // FIXME: add some template or macro to avoid casting - SkOpAngle& angle(int index) { - const SkOpAngle& cAngle = (const_cast<const SkOpSegment*>(this))->angle(index); - return const_cast<SkOpAngle&>(cAngle); - } + struct AlignedSpan { + double fOldT; + double fT; + SkPoint fOldPt; + SkPoint fPt; + const SkOpSegment* fSegment; + const SkOpSegment* fOther1; + const SkOpSegment* fOther2; + }; const SkPathOpsBounds& bounds() const { return fBounds; @@ -81,6 +86,10 @@ public: return dxdy(index).fY; } + bool hasMultiples() const { + return fMultiples; + } + bool hasSmall() const { return fSmall; } @@ -185,8 +194,7 @@ public: const SkOpAngle* spanToAngle(int tStart, int tEnd) const { SkASSERT(tStart != tEnd); const SkOpSpan& span = fTs[tStart]; - int index = tStart < tEnd ? span.fToAngleIndex : span.fFromAngleIndex; - return index >= 0 ? &angle(index) : NULL; + return tStart < tEnd ? span.fToAngle : span.fFromAngle; } // FIXME: create some sort of macro or template that avoids casting @@ -278,11 +286,19 @@ public: SkOpSegment* other); const SkOpSpan* addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, const SkPoint& pt); + const SkOpSpan* addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, + const SkPoint& pt, const SkPoint& oPt); + void alignMultiples(SkTDArray<AlignedSpan>* aligned); bool alignSpan(int index, double thisT, const SkPoint& thisPt); void alignSpanState(int start, int end); - const SkOpAngle& angle(int index) const; bool betweenTs(int lesser, double testT, int greater) const; + void blindCancel(const SkCoincidence& coincidence, SkOpSegment* other); + void blindCoincident(const SkCoincidence& coincidence, SkOpSegment* other); bool calcAngles(); + double calcMissingTEnd(const SkOpSegment* ref, double loEnd, double min, double max, + double hiEnd, const SkOpSegment* other, int thisEnd); + double calcMissingTStart(const SkOpSegment* ref, double loEnd, double min, double max, + double hiEnd, const SkOpSegment* other, int thisEnd); void checkDuplicates(); void checkEnds(); void checkMultiples(); @@ -301,6 +317,7 @@ public: bool* unsortable); SkOpSegment* findNextXor(int* nextStart, int* nextEnd, bool* unsortable); int findExactT(double t, const SkOpSegment* ) const; + int findOtherT(double t, const SkOpSegment* ) const; int findT(double t, const SkPoint& , const SkOpSegment* ) const; SkOpSegment* findTop(int* tIndex, int* endIndex, bool* unsortable, bool firstPass); void fixOtherTIndex(); @@ -321,6 +338,7 @@ public: void markDoneUnary(int index); bool nextCandidate(int* start, int* end) const; int nextSpan(int from, int step) const; + void pinT(const SkPoint& pt, double* t); void setUpWindings(int index, int endIndex, int* sumMiWinding, int* sumSuWinding, int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding); void sortAngles(); @@ -334,9 +352,6 @@ public: int windingAtT(double tHit, int tIndex, bool crossOpp, SkScalar* dx) const; int windSum(const SkOpAngle* angle) const; // available for testing only -#if DEBUG_VALIDATE - bool debugContains(const SkOpAngle* ) const; -#endif #if defined(SK_DEBUG) || !FORCE_RELEASE int debugID() const { return fID; @@ -358,6 +373,7 @@ public: const SkTDArray<SkOpSpan>& debugSpans() const; void debugValidate() const; // available to testing only + const SkOpAngle* debugLastAngle() const; void dumpAngles() const; void dumpContour(int firstID, int lastID) const; void dumpPts() const; @@ -382,18 +398,22 @@ private: bool activeWinding(int index, int endIndex, int* sumWinding); void addCancelOutsides(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other); void addCoinOutsides(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other); - int addSingletonAngleDown(int start, SkOpSegment** otherPtr); - int addSingletonAngleUp(int start, SkOpSegment** otherPtr); - SkOpAngle* addSingletonAngles(int start, int step); - void addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, const SkPoint& pt, - const SkPoint& oPt); + SkOpAngle* addSingletonAngleDown(SkOpSegment** otherPtr, SkOpAngle** ); + SkOpAngle* addSingletonAngleUp(SkOpSegment** otherPtr, SkOpAngle** ); + SkOpAngle* addSingletonAngles(int step); + void alignSpan(const SkPoint& newPt, double newT, const SkOpSegment* other, double otherT, + const SkOpSegment* other2, SkOpSpan* oSpan, SkTDArray<AlignedSpan>* ); bool betweenPoints(double midT, const SkPoint& pt1, const SkPoint& pt2) const; + void bumpCoincidentBlind(bool binary, int index, int last); void bumpCoincidentThis(const SkOpSpan& oTest, bool binary, int* index, SkTArray<SkPoint, true>* outsideTs); + void bumpCoincidentOBlind(int index, int last); void bumpCoincidentOther(const SkOpSpan& oTest, int* index, SkTArray<SkPoint, true>* outsideTs); bool bumpSpan(SkOpSpan* span, int windDelta, int oppDelta); bool calcLoopSpanCount(const SkOpSpan& thisSpan, int* smallCounts); + bool checkForSmall(const SkOpSpan* span, const SkPoint& pt, double newT, + int* less, int* more) const; void checkLinks(const SkOpSpan* , SkTArray<MissingSpan, true>* missingSpans) const; static void CheckOneLink(const SkOpSpan* test, const SkOpSpan* oSpan, @@ -402,19 +422,26 @@ private: SkTArray<MissingSpan, true>* missingSpans); int checkSetAngle(int tIndex) const; void checkSmallCoincidence(const SkOpSpan& span, SkTArray<MissingSpan, true>* ); + bool coincidentSmall(const SkPoint& pt, double t, const SkOpSegment* other) const; bool clockwise(int tStart, int tEnd) const; static void ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, SkOpAngle::IncludeType ); static void ComputeOneSumReverse(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, SkOpAngle::IncludeType ); + bool containsT(double t, const SkOpSegment* other, double otherT) const; bool decrementSpan(SkOpSpan* span); int findEndSpan(int endIndex) const; int findStartSpan(int startIndex) const; int firstActive(int tIndex) const; const SkOpSpan& firstSpan(const SkOpSpan& thisSpan) const; void init(const SkPoint pts[], SkPath::Verb verb, bool operand, bool evenOdd); + bool inCoincidentSpan(double t, const SkOpSegment* other) const; bool inLoop(const SkOpAngle* baseAngle, int spanCount, int* indexPtr) const; +#if OLD_CHASE bool isSimple(int end) const; +#else + SkOpSegment* isSimple(int* end, int* step); +#endif bool isTiny(int index) const; const SkOpSpan& lastSpan(const SkOpSpan& thisSpan) const; void matchWindingValue(int tIndex, double t, bool borrowWind); @@ -436,20 +463,15 @@ private: void markWinding(int index, int winding, int oppWinding); bool monotonicInY(int tStart, int tEnd) const; - bool multipleEnds() const { - return fTs[count() - 2].fT == 1; - } - - bool multipleStarts() const { - return fTs[1].fT == 0; - } + bool multipleEnds() const { return fTs[count() - 2].fT == 1; } + bool multipleStarts() const { return fTs[1].fT == 0; } - bool multipleSpans(int end) const; - SkOpSegment* nextChase(int* index, const int step, int* min, SkOpSpan** last); + SkOpSegment* nextChase(int* index, int* step, int* min, SkOpSpan** last); int nextExactSpan(int from, int step) const; bool serpentine(int tStart, int tEnd) const; - void setFromAngleIndex(int endIndex, int angleIndex); - void setToAngleIndex(int endIndex, int angleIndex); + void setCoincidentRange(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other); + void setFromAngle(int endIndex, SkOpAngle* ); + void setToAngle(int endIndex, SkOpAngle* ); void setUpWindings(int index, int endIndex, int* sumMiWinding, int* maxWinding, int* sumWinding); void subDivideBounds(int start, int end, SkPathOpsBounds* bounds) const; @@ -509,14 +531,13 @@ private: SkPathOpsBounds fBounds; // FIXME: can't convert to SkTArray because it uses insert SkTDArray<SkOpSpan> fTs; // 2+ (always includes t=0 t=1) -- at least (number of spans) + 1 -// FIXME: replace both with bucket storage that allows direct immovable pointers to angles - SkTArray<SkOpAngle, true> fSingletonAngles; // 0 or 2 -- allocated for singletons - SkTArray<SkOpAngle, true> fAngles; // 0 or 2+ -- (number of non-zero spans) * 2 + SkOpAngleSet fAngles; // empty or 2+ -- (number of non-zero spans) * 2 // OPTIMIZATION: could pack donespans, verb, operand, xor into 1 int-sized value int fDoneSpans; // quick check that segment is finished // OPTIMIZATION: force the following to be byte-sized SkPath::Verb fVerb; bool fLoop; // set if cubic intersects itself + bool fMultiples; // set if curve intersects multiple other curves at one interior point bool fOperand; bool fXor; // set if original contour had even-odd fill bool fOppXor; // set if opposite operand had even-odd fill diff --git a/src/pathops/SkOpSpan.h b/src/pathops/SkOpSpan.h index 1ffdc0e2d2..d9ce44eb77 100644 --- a/src/pathops/SkOpSpan.h +++ b/src/pathops/SkOpSpan.h @@ -9,23 +9,27 @@ #include "SkPoint.h" +class SkOpAngle; class SkOpSegment; struct SkOpSpan { - SkOpSegment* fOther; SkPoint fPt; // computed when the curves are intersected double fT; double fOtherT; // value at fOther[fOtherIndex].fT + SkOpSegment* fOther; + SkOpAngle* fFromAngle; // (if t > 0) index into segment's angle array going negative in t + SkOpAngle* fToAngle; // (if t < 1) index into segment's angle array going positive in t int fOtherIndex; // can't be used during intersection - int fFromAngleIndex; // (if t > 0) index into segment's angle array going negative in t - int fToAngleIndex; // (if t < 1) index into segment's angle array going positive in t int fWindSum; // accumulated from contours surrounding this one. int fOppSum; // for binary operators: the opposite winding sum int fWindValue; // 0 == canceled; 1 == normal; >1 == coincident int fOppValue; // normally 0 -- when binary coincident edges combine, opp value goes here bool fChased; // set after span has been added to chase array + bool fCoincident; // set if span is bumped -- if set additional points aren't inserted bool fDone; // if set, this span to next higher T has been processed bool fLoop; // set when a cubic loops back to this point + bool fMultiple; // set if this is one of mutiple spans with identical t and pt values + bool fNear; // set if opposite end point is near but not equal to this one bool fSmall; // if set, consecutive points are almost equal bool fTiny; // if set, consecutive points are equal but consecutive ts are not precisely equal diff --git a/src/pathops/SkPathOpsCommon.cpp b/src/pathops/SkPathOpsCommon.cpp index 0e9e1bee8e..9a8a2cf4e3 100644 --- a/src/pathops/SkPathOpsCommon.cpp +++ b/src/pathops/SkPathOpsCommon.cpp @@ -10,6 +10,29 @@ #include "SkPathWriter.h" #include "SkTSort.h" +static void alignMultiples(SkTArray<SkOpContour*, true>* contourList, + SkTDArray<SkOpSegment::AlignedSpan>* aligned) { + int contourCount = (*contourList).count(); + for (int cTest = 0; cTest < contourCount; ++cTest) { + SkOpContour* contour = (*contourList)[cTest]; + if (contour->hasMultiples()) { + contour->alignMultiples(aligned); + } + } +} + +static void alignCoincidence(SkTArray<SkOpContour*, true>* contourList, + const SkTDArray<SkOpSegment::AlignedSpan>& aligned) { + int contourCount = (*contourList).count(); + for (int cTest = 0; cTest < contourCount; ++cTest) { + SkOpContour* contour = (*contourList)[cTest]; + int count = aligned.count(); + for (int index = 0; index < count; ++index) { + contour->alignCoincidence(aligned[index]); + } + } +} + static int contourRangeCheckY(const SkTArray<SkOpContour*, true>& contourList, SkOpSegment** currentPtr, int* indexPtr, int* endIndexPtr, double* bestHit, SkScalar* bestDx, bool* tryAgain, double* midPtr, bool opp) { @@ -185,7 +208,7 @@ SkOpSegment* FindChase(SkTDArray<SkOpSpan*>* chase, int* tIndex, int* endIndex) if (SkOpSegment::UseInnerWinding(maxWinding, winding)) { maxWinding = winding; } - segment->markAndChaseWinding(angle, maxWinding, 0); + (void) segment->markAndChaseWinding(angle, maxWinding, 0); break; } } @@ -204,9 +227,8 @@ void DebugShowActiveSpans(SkTArray<SkOpContour*, true>& contourList) { } #endif -static SkOpSegment* findSortableTop(const SkTArray<SkOpContour*, true>& contourList, - int* index, int* endIndex, SkPoint* topLeft, bool* unsortable, - bool* done, bool firstPass) { +static SkOpSegment* findTopSegment(const SkTArray<SkOpContour*, true>& contourList, int* index, + int* endIndex, SkPoint* topLeft, bool* unsortable, bool* done, bool firstPass) { SkOpSegment* result; const SkOpSegment* lastTopStart = NULL; int lastIndex = -1, lastEndIndex = -1; @@ -249,28 +271,27 @@ static SkOpSegment* findSortableTop(const SkTArray<SkOpContour*, true>& contourL lastEndIndex = *endIndex; } } while (!result); -#if 0 - if (result) { - *unsortable = false; - } -#endif return result; } static int rightAngleWinding(const SkTArray<SkOpContour*, true>& contourList, - SkOpSegment** current, int* index, int* endIndex, double* tHit, - SkScalar* hitDx, bool* tryAgain, bool opp) { + SkOpSegment** currentPtr, int* indexPtr, int* endIndexPtr, double* tHit, + SkScalar* hitDx, bool* tryAgain, bool* onlyVertical, bool opp) { double test = 0.9; int contourWinding; do { - contourWinding = contourRangeCheckY(contourList, current, index, endIndex, tHit, hitDx, - tryAgain, &test, opp); + contourWinding = contourRangeCheckY(contourList, currentPtr, indexPtr, endIndexPtr, + tHit, hitDx, tryAgain, &test, opp); if (contourWinding != SK_MinS32 || *tryAgain) { return contourWinding; } + if (*currentPtr && (*currentPtr)->isVertical()) { + *onlyVertical = true; + return contourWinding; + } test /= 2; } while (!approximately_negative(test)); - SkASSERT(0); // should be OK to comment out, but interested when this hits + SkASSERT(0); // FIXME: incomplete functionality return contourWinding; } @@ -296,30 +317,34 @@ static void skipVertical(const SkTArray<SkOpContour*, true>& contourList, SkOpSegment* FindSortableTop(const SkTArray<SkOpContour*, true>& contourList, SkOpAngle::IncludeType angleIncludeType, bool* firstContour, int* indexPtr, - int* endIndexPtr, SkPoint* topLeft, bool* unsortable, bool* done, bool firstPass) { - SkOpSegment* current = findSortableTop(contourList, indexPtr, endIndexPtr, topLeft, unsortable, + int* endIndexPtr, SkPoint* topLeft, bool* unsortable, bool* done, bool* onlyVertical, + bool firstPass) { + SkOpSegment* current = findTopSegment(contourList, indexPtr, endIndexPtr, topLeft, unsortable, done, firstPass); if (!current) { return NULL; } - const int index = *indexPtr; + const int startIndex = *indexPtr; const int endIndex = *endIndexPtr; if (*firstContour) { - current->initWinding(index, endIndex, angleIncludeType); + current->initWinding(startIndex, endIndex, angleIncludeType); *firstContour = false; return current; } - int minIndex = SkMin32(index, endIndex); + int minIndex = SkMin32(startIndex, endIndex); int sumWinding = current->windSum(minIndex); - if (sumWinding != SK_MinS32) { - return current; - } - SkASSERT(current->windSum(SkMin32(index, endIndex)) == SK_MinS32); - const SkOpSpan& span = current->span(endIndex); - if ((index < endIndex ? span.fFromAngleIndex : span.fToAngleIndex) < 0) { - current->addSimpleAngle(endIndex); + if (sumWinding == SK_MinS32) { + int index = endIndex; + int oIndex = startIndex; + do { + const SkOpSpan& span = current->span(index); + if ((oIndex < index ? span.fFromAngle : span.fToAngle) == NULL) { + current->addSimpleAngle(index); + } + sumWinding = current->computeSum(oIndex, index, angleIncludeType); + SkTSwap(index, oIndex); + } while (sumWinding == SK_MinS32 && index == startIndex); } - sumWinding = current->computeSum(index, endIndex, angleIncludeType); if (sumWinding != SK_MinS32 && sumWinding != SK_NaN32) { return current; } @@ -340,7 +365,10 @@ SkOpSegment* FindSortableTop(const SkTArray<SkOpContour*, true>& contourList, SkASSERT(*indexPtr != *endIndexPtr && *indexPtr >= 0 && *endIndexPtr >= 0); tryAgain = false; contourWinding = rightAngleWinding(contourList, ¤t, indexPtr, endIndexPtr, &tHit, - &hitDx, &tryAgain, false); + &hitDx, &tryAgain, onlyVertical, false); + if (*onlyVertical) { + return current; + } if (tryAgain) { continue; } @@ -348,7 +376,7 @@ SkOpSegment* FindSortableTop(const SkTArray<SkOpContour*, true>& contourList, break; } oppContourWinding = rightAngleWinding(contourList, ¤t, indexPtr, endIndexPtr, &tHit, - &hitOppDx, &tryAgain, true); + &hitOppDx, &tryAgain, NULL, true); } while (tryAgain); current->initWinding(*indexPtr, *endIndexPtr, tHit, contourWinding, hitDx, oppContourWinding, hitOppDx); @@ -387,14 +415,15 @@ static void checkEnds(SkTArray<SkOpContour*, true>* contourList) { } } -static void checkMultiples(SkTArray<SkOpContour*, true>* contourList) { - // it's hard to determine if the end of a cubic or conic nearly intersects another curve. - // instead, look to see if the connecting curve intersected at that same end. +static bool checkMultiples(SkTArray<SkOpContour*, true>* contourList) { + bool hasMultiples = false; int contourCount = (*contourList).count(); for (int cTest = 0; cTest < contourCount; ++cTest) { SkOpContour* contour = (*contourList)[cTest]; contour->checkMultiples(); + hasMultiples |= contour->hasMultiples(); } + return hasMultiples; } // A small interval of a pair of curves may collapse to lines for each, triggering coincidence @@ -675,12 +704,17 @@ bool HandleCoincidence(SkTArray<SkOpContour*, true>* contourList, int total) { SkOpContour::debugShowWindingValues(contourList); #endif fixOtherTIndex(contourList); - checkEnds(contourList); - checkMultiples(contourList); - checkDuplicates(contourList); - checkTiny(contourList); - checkSmall(contourList); - joinCoincidence(contourList); + checkEnds(contourList); // check if connecting curve intersected at the same end + bool hasM = checkMultiples(contourList); // check if intersections agree on t and point values + SkTDArray<SkOpSegment::AlignedSpan> aligned; + if (hasM) { + alignMultiples(contourList, &aligned); // align pairs of identical points + alignCoincidence(contourList, aligned); + } + checkDuplicates(contourList); // check if spans have the same number on the other end + checkTiny(contourList); // if pair have the same end points, mark them as parallel + checkSmall(contourList); // a pair of curves with a small span may turn into coincident lines + joinCoincidence(contourList); // join curves that connect to a coincident pair sortSegments(contourList); if (!calcAngles(contourList)) { return false; diff --git a/src/pathops/SkPathOpsCommon.h b/src/pathops/SkPathOpsCommon.h index 6a7bb724be..0d8cfc42f9 100644 --- a/src/pathops/SkPathOpsCommon.h +++ b/src/pathops/SkPathOpsCommon.h @@ -18,7 +18,7 @@ void Assemble(const SkPathWriter& path, SkPathWriter* simple); SkOpSegment* FindChase(SkTDArray<SkOpSpan*>* chase, int* tIndex, int* endIndex); SkOpSegment* FindSortableTop(const SkTArray<SkOpContour*, true>& , SkOpAngle::IncludeType , bool* firstContour, int* index, int* endIndex, SkPoint* topLeft, - bool* unsortable, bool* done, bool firstPass); + bool* unsortable, bool* done, bool* onlyVertical, bool firstPass); SkOpSegment* FindUndone(SkTArray<SkOpContour*, true>& contourList, int* start, int* end); void MakeContourList(SkTArray<SkOpContour>& contours, SkTArray<SkOpContour*, true>& list, bool evenOdd, bool oppEvenOdd); diff --git a/src/pathops/SkPathOpsDebug.cpp b/src/pathops/SkPathOpsDebug.cpp index 56813b8b1f..9d82dffc1a 100644 --- a/src/pathops/SkPathOpsDebug.cpp +++ b/src/pathops/SkPathOpsDebug.cpp @@ -104,68 +104,7 @@ void SkPathOpsDebug::ShowPath(const SkPath& one, const SkPath& two, SkPathOp op, #if DEBUG_SORT void SkOpAngle::debugLoop() const { - const SkOpAngle* first = this; - const SkOpAngle* next = this; - do { - next->debugOne(true); - SkDebugf("\n"); - next = next->fNext; - } while (next && next != first); -} - -void SkOpAngle::debugOne(bool functionHeader) const { -// fSegment->debugValidate(); - const SkOpSpan& mSpan = fSegment->span(SkMin32(fStart, fEnd)); - if (functionHeader) { - SkDebugf("%s ", __FUNCTION__); - } - SkDebugf("[%d", fSegment->debugID()); -#if DEBUG_ANGLE - SkDebugf("/%d", fID); -#endif - SkDebugf("] next="); - if (fNext) { - SkDebugf("%d", fNext->fSegment->debugID()); -#if DEBUG_ANGLE - SkDebugf("/%d", fNext->fID); -#endif - } else { - SkDebugf("?"); - } - SkDebugf(" sect=%d/%d ", fSectorStart, fSectorEnd); - SkDebugf(" s=%1.9g [%d] e=%1.9g [%d]", fSegment->span(fStart).fT, fStart, - fSegment->span(fEnd).fT, fEnd); - SkDebugf(" sgn=%d windVal=%d", sign(), mSpan.fWindValue); - -#if DEBUG_WINDING - SkDebugf(" windSum="); - SkPathOpsDebug::WindingPrintf(mSpan.fWindSum); -#endif - if (mSpan.fOppValue != 0 || mSpan.fOppSum != SK_MinS32) { - SkDebugf(" oppVal=%d", mSpan.fOppValue); -#if DEBUG_WINDING - SkDebugf(" oppSum="); - SkPathOpsDebug::WindingPrintf(mSpan.fOppSum); -#endif - } - if (mSpan.fDone) { - SkDebugf(" done"); - } - if (unorderable()) { - SkDebugf(" unorderable"); - } - if (small()) { - SkDebugf(" small"); - } - if (mSpan.fTiny) { - SkDebugf(" tiny"); - } - if (fSegment->operand()) { - SkDebugf(" operand"); - } - if (fStop) { - SkDebugf(" stop"); - } + dumpLoop(); } #endif @@ -174,12 +113,12 @@ void SkOpAngle::debugSameAs(const SkOpAngle* compare) const { SK_ALWAYSBREAK(fSegment == compare->fSegment); const SkOpSpan& startSpan = fSegment->span(fStart); const SkOpSpan& oStartSpan = fSegment->span(compare->fStart); - SK_ALWAYSBREAK(startSpan.fToAngleIndex == oStartSpan.fToAngleIndex); - SK_ALWAYSBREAK(startSpan.fFromAngleIndex == oStartSpan.fFromAngleIndex); + SK_ALWAYSBREAK(startSpan.fToAngle == oStartSpan.fToAngle); + SK_ALWAYSBREAK(startSpan.fFromAngle == oStartSpan.fFromAngle); const SkOpSpan& endSpan = fSegment->span(fEnd); const SkOpSpan& oEndSpan = fSegment->span(compare->fEnd); - SK_ALWAYSBREAK(endSpan.fToAngleIndex == oEndSpan.fToAngleIndex); - SK_ALWAYSBREAK(endSpan.fFromAngleIndex == oEndSpan.fFromAngleIndex); + SK_ALWAYSBREAK(endSpan.fToAngle == oEndSpan.fToAngle); + SK_ALWAYSBREAK(endSpan.fFromAngle == oEndSpan.fFromAngle); } #endif @@ -189,7 +128,7 @@ void SkOpAngle::debugValidateNext() const { const SkOpAngle* next = first; SkTDArray<const SkOpAngle*>(angles); do { - SK_ALWAYSBREAK(next->fSegment->debugContains(next)); +// SK_ALWAYSBREAK(next->fSegment->debugContains(next)); angles.push(next); next = next->next(); if (next == first) { @@ -377,22 +316,6 @@ void SkOpSegment::debugCheckPointsEqualish(int tStart, int tEnd) const { } #endif -#if DEBUG_VALIDATE -bool SkOpSegment::debugContains(const SkOpAngle* angle) const { - for (int index = 0; index < fAngles.count(); ++index) { - if (&fAngles[index] == angle) { - return true; - } - } - for (int index = 0; index < fSingletonAngles.count(); ++index) { - if (&fSingletonAngles[index] == angle) { - return true; - } - } - return false; -} -#endif - #if DEBUG_SWAP_TOP int SkOpSegment::debugInflections(int tStart, int tEnd) const { if (fVerb != SkPath::kCubic_Verb) { @@ -404,6 +327,19 @@ int SkOpSegment::debugInflections(int tStart, int tEnd) const { } #endif +const SkOpAngle* SkOpSegment::debugLastAngle() const { + const SkOpAngle* result = NULL; + for (int index = 0; index < count(); ++index) { + const SkOpSpan& span = this->span(index); + if (span.fToAngle) { + SkASSERT(!result); + result = span.fToAngle; + } + } + SkASSERT(result); + return result; +} + void SkOpSegment::debugReset() { fTs.reset(); fAngles.reset(); @@ -539,7 +475,7 @@ void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan& span, int } else { SkDebugf("%d", span.fWindSum); } - SkDebugf(" windValue=%d\n", span.fWindValue); + SkDebugf(" windValue=%d oppValue=%d\n", span.fWindValue, span.fOppValue); } #endif @@ -590,6 +526,7 @@ void SkOpSegment::debugValidate() const { SK_ALWAYSBREAK(&fTs[i] == &otherSpan.fOther->fTs[otherSpan.fOtherIndex]); done += span.fDone; if (last) { + SK_ALWAYSBREAK(last->fT != span.fT || last->fOther != span.fOther); bool tsEqual = last->fT == span.fT; bool tsPreciselyEqual = precisely_equal(last->fT, span.fT); SK_ALWAYSBREAK(!tsEqual || tsPreciselyEqual); @@ -616,8 +553,8 @@ void SkOpSegment::debugValidate() const { hasLoop |= last->fLoop; } SK_ALWAYSBREAK(done == fDoneSpans); - if (fAngles.count() ) { - fAngles.begin()->debugValidateLoop(); - } +// if (fAngles.count() ) { +// fAngles.begin()->debugValidateLoop(); +// } #endif } diff --git a/src/pathops/SkPathOpsDebug.h b/src/pathops/SkPathOpsDebug.h index bb54039244..9dc562fea1 100644 --- a/src/pathops/SkPathOpsDebug.h +++ b/src/pathops/SkPathOpsDebug.h @@ -51,6 +51,7 @@ #define DEBUG_CONCIDENT 0 #define DEBUG_CROSS 0 #define DEBUG_CUBIC_BINARY_SEARCH 0 +#define DEBUG_DUPLICATES 0 #define DEBUG_FLAT_QUADS 0 #define DEBUG_FLOW 0 #define DEBUG_LIMIT_WIND_SUM 0 @@ -86,6 +87,7 @@ #define DEBUG_CONCIDENT 1 #define DEBUG_CROSS 01 #define DEBUG_CUBIC_BINARY_SEARCH 1 +#define DEBUG_DUPLICATES 1 #define DEBUG_FLAT_QUADS 0 #define DEBUG_FLOW 1 #define DEBUG_LIMIT_WIND_SUM 4 @@ -123,7 +125,7 @@ #define CUBIC_DEBUG_DATA(c) c[0].fX, c[0].fY, c[1].fX, c[1].fY, c[2].fX, c[2].fY, c[3].fX, c[3].fY #define QUAD_DEBUG_DATA(q) q[0].fX, q[0].fY, q[1].fX, q[1].fY, q[2].fX, q[2].fY #define LINE_DEBUG_DATA(l) l[0].fX, l[0].fY, l[1].fX, l[1].fY -#define PT_DEBUG_DATA(i, n) i.pt(n).fX, i.pt(n).fY +#define PT_DEBUG_DATA(i, n) i.pt(n).asSkPoint().fX, i.pt(n).asSkPoint().fY #ifndef DEBUG_TEST #define DEBUG_TEST 0 @@ -168,14 +170,18 @@ public: static void BumpTestName(char* ); #endif static void ShowPath(const SkPath& one, const SkPath& two, SkPathOp op, const char* name); - static void DumpAngles(const SkTArray<class SkOpAngle, true>& angles); - static void DumpAngles(const SkTArray<class SkOpAngle* , true>& angles); + static void DumpCoincidence(const SkTArray<class SkOpContour, true>& contours); + static void DumpCoincidence(const SkTArray<class SkOpContour* , true>& contours); static void DumpContours(const SkTArray<class SkOpContour, true>& contours); static void DumpContours(const SkTArray<class SkOpContour* , true>& contours); static void DumpContourAngles(const SkTArray<class SkOpContour, true>& contours); static void DumpContourAngles(const SkTArray<class SkOpContour* , true>& contours); + static void DumpContourPt(const SkTArray<class SkOpContour, true>& contours, int id); + static void DumpContourPt(const SkTArray<class SkOpContour* , true>& contours, int id); static void DumpContourPts(const SkTArray<class SkOpContour, true>& contours); static void DumpContourPts(const SkTArray<class SkOpContour* , true>& contours); + static void DumpContourSpan(const SkTArray<class SkOpContour, true>& contours, int id); + static void DumpContourSpan(const SkTArray<class SkOpContour* , true>& contours, int id); static void DumpContourSpans(const SkTArray<class SkOpContour, true>& contours); static void DumpContourSpans(const SkTArray<class SkOpContour* , true>& contours); static void DumpSpans(const SkTDArray<struct SkOpSpan *>& ); @@ -183,34 +189,44 @@ public: }; // shorthand for calling from debugger -void Dump(const SkTArray<class SkOpAngle, true>& angles); -void Dump(const SkTArray<class SkOpAngle* , true>& angles); -void Dump(const SkTArray<class SkOpAngle, true>* angles); -void Dump(const SkTArray<class SkOpAngle* , true>* angles); - void Dump(const SkTArray<class SkOpContour, true>& contours); void Dump(const SkTArray<class SkOpContour* , true>& contours); void Dump(const SkTArray<class SkOpContour, true>* contours); void Dump(const SkTArray<class SkOpContour* , true>* contours); -void Dump(const SkTDArray<SkOpSpan *>& chaseArray); -void Dump(const SkTDArray<SkOpSpan *>* chaseArray); +void Dump(const SkTDArray<SkOpSpan* >& chase); +void Dump(const SkTDArray<SkOpSpan* >* chase); void DumpAngles(const SkTArray<class SkOpContour, true>& contours); void DumpAngles(const SkTArray<class SkOpContour* , true>& contours); void DumpAngles(const SkTArray<class SkOpContour, true>* contours); void DumpAngles(const SkTArray<class SkOpContour* , true>* contours); +void DumpCoin(const SkTArray<class SkOpContour, true>& contours); +void DumpCoin(const SkTArray<class SkOpContour* , true>& contours); +void DumpCoin(const SkTArray<class SkOpContour, true>* contours); +void DumpCoin(const SkTArray<class SkOpContour* , true>* contours); + void DumpPts(const SkTArray<class SkOpContour, true>& contours); void DumpPts(const SkTArray<class SkOpContour* , true>& contours); void DumpPts(const SkTArray<class SkOpContour, true>* contours); void DumpPts(const SkTArray<class SkOpContour* , true>* contours); +void DumpPt(const SkTArray<class SkOpContour, true>& contours, int segmentID); +void DumpPt(const SkTArray<class SkOpContour* , true>& contours, int segmentID); +void DumpPt(const SkTArray<class SkOpContour, true>* contours, int segmentID); +void DumpPt(const SkTArray<class SkOpContour* , true>* contours, int segmentID); + void DumpSpans(const SkTArray<class SkOpContour, true>& contours); void DumpSpans(const SkTArray<class SkOpContour* , true>& contours); void DumpSpans(const SkTArray<class SkOpContour, true>* contours); void DumpSpans(const SkTArray<class SkOpContour* , true>* contours); +void DumpSpan(const SkTArray<class SkOpContour, true>& contours, int segmentID); +void DumpSpan(const SkTArray<class SkOpContour* , true>& contours, int segmentID); +void DumpSpan(const SkTArray<class SkOpContour, true>* contours, int segmentID); +void DumpSpan(const SkTArray<class SkOpContour* , true>* contours, int segmentID); + // generates tools/path_sorter.htm and path_visualizer.htm compatible data void DumpQ(const struct SkDQuad& quad1, const struct SkDQuad& quad2, int testNo); diff --git a/src/pathops/SkPathOpsLine.cpp b/src/pathops/SkPathOpsLine.cpp index 7587fda69a..622961911a 100644 --- a/src/pathops/SkPathOpsLine.cpp +++ b/src/pathops/SkPathOpsLine.cpp @@ -63,7 +63,7 @@ double SkDLine::exactPoint(const SkDPoint& xy) const { return -1; } -double SkDLine::nearPoint(const SkDPoint& xy) const { +double SkDLine::nearPoint(const SkDPoint& xy, bool* unequal) const { if (!AlmostBetweenUlps(fPts[0].fX, xy.fX, fPts[1].fX) || !AlmostBetweenUlps(fPts[0].fY, xy.fY, fPts[1].fY)) { return -1; @@ -86,6 +86,9 @@ double SkDLine::nearPoint(const SkDPoint& xy) const { if (!AlmostEqualUlps(largest, largest + dist)) { // is the dist within ULPS tolerance? return -1; } + if (unequal) { + *unequal = (float) largest != (float) (largest + dist); + } t = SkPinT(t); SkASSERT(between(0, t, 1)); return t; diff --git a/src/pathops/SkPathOpsLine.h b/src/pathops/SkPathOpsLine.h index e4ed0c9b20..74eb615348 100644 --- a/src/pathops/SkPathOpsLine.h +++ b/src/pathops/SkPathOpsLine.h @@ -30,7 +30,7 @@ struct SkDLine { static double ExactPointH(const SkDPoint& xy, double left, double right, double y); static double ExactPointV(const SkDPoint& xy, double top, double bottom, double x); double isLeft(const SkDPoint& pt) const; - double nearPoint(const SkDPoint& xy) const; + double nearPoint(const SkDPoint& xy, bool* unequal) const; bool nearRay(const SkDPoint& xy) const; static double NearPointH(const SkDPoint& xy, double left, double right, double y); static double NearPointV(const SkDPoint& xy, double top, double bottom, double x); diff --git a/src/pathops/SkPathOpsOp.cpp b/src/pathops/SkPathOpsOp.cpp index 5af4753b50..4c6923abb6 100644 --- a/src/pathops/SkPathOpsOp.cpp +++ b/src/pathops/SkPathOpsOp.cpp @@ -41,6 +41,9 @@ static SkOpSegment* findChaseOp(SkTDArray<SkOpSpan*>& chase, int* tIndex, int* e } // find first angle, initialize winding to computed fWindSum const SkOpAngle* angle = segment->spanToAngle(*tIndex, *endIndex); + if (!angle) { + continue; + } const SkOpAngle* firstAngle = angle; SkDEBUGCODE(bool loop = false); int winding; @@ -70,6 +73,7 @@ static SkOpSegment* findChaseOp(SkTDArray<SkOpSpan*>& chase, int* tIndex, int* e *tIndex = start; *endIndex = end; } + // OPTIMIZATION: should this also add to the chase? (void) segment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding, angle); } @@ -125,9 +129,10 @@ static bool bridgeOp(SkTArray<SkOpContour*, true>& contourList, const SkPathOp o do { int index, endIndex; bool topDone; + bool onlyVertical = false; lastTopLeft = topLeft; SkOpSegment* current = FindSortableTop(contourList, SkOpAngle::kBinarySingle, &firstContour, - &index, &endIndex, &topLeft, &topUnsortable, &topDone, firstPass); + &index, &endIndex, &topLeft, &topUnsortable, &topDone, &onlyVertical, firstPass); if (!current) { if ((!topUnsortable || firstPass) && !topDone) { SkASSERT(topLeft.fX != SK_ScalarMin && topLeft.fY != SK_ScalarMin); @@ -142,29 +147,33 @@ static bool bridgeOp(SkTArray<SkOpContour*, true>& contourList, const SkPathOp o continue; } break; + } else if (onlyVertical) { + break; } firstPass = !topUnsortable || lastTopLeft != topLeft; - SkTDArray<SkOpSpan*> chaseArray; + SkTDArray<SkOpSpan*> chase; do { if (current->activeOp(index, endIndex, xorMask, xorOpMask, op)) { do { if (!unsortable && current->done()) { - if (simple->isEmpty()) { - simple->init(); - } break; } SkASSERT(unsortable || !current->done()); int nextStart = index; int nextEnd = endIndex; - SkOpSegment* next = current->findNextOp(&chaseArray, &nextStart, &nextEnd, + SkOpSegment* next = current->findNextOp(&chase, &nextStart, &nextEnd, &unsortable, op, xorMask, xorOpMask); if (!next) { if (!unsortable && simple->hasMove() && current->verb() != SkPath::kLine_Verb && !simple->isClosed()) { current->addCurveTo(index, endIndex, simple, true); - SkASSERT(simple->isClosed()); + #if DEBUG_ACTIVE_SPANS + if (!simple->isClosed()) { + DebugShowActiveSpans(contourList); + } + #endif +// SkASSERT(simple->isClosed()); } break; } @@ -195,11 +204,16 @@ static bool bridgeOp(SkTArray<SkOpContour*, true>& contourList, const SkPathOp o SkOpSpan* last = current->markAndChaseDoneBinary(index, endIndex); if (last && !last->fChased && !last->fLoop) { last->fChased = true; - SkASSERT(!SkPathOpsDebug::ChaseContains(chaseArray, last)); - *chaseArray.append() = last; + SkASSERT(!SkPathOpsDebug::ChaseContains(chase, last)); + *chase.append() = last; +#if DEBUG_WINDING + SkDebugf("%s chase.append id=%d windSum=%d small=%d\n", __FUNCTION__, + last->fOther->span(last->fOtherIndex).fOther->debugID(), last->fWindSum, + last->fSmall); +#endif } } - current = findChaseOp(chaseArray, &index, &endIndex); + current = findChaseOp(chase, &index, &endIndex); #if DEBUG_ACTIVE_SPANS DebugShowActiveSpans(contourList); #endif diff --git a/src/pathops/SkPathOpsPoint.h b/src/pathops/SkPathOpsPoint.h index 336fb62de0..5c2e3a50dd 100644 --- a/src/pathops/SkPathOpsPoint.h +++ b/src/pathops/SkPathOpsPoint.h @@ -148,14 +148,12 @@ struct SkDPoint { return AlmostBequalUlps((double) largest, largest + dist); // is dist within ULPS tolerance? } -#ifdef SK_DEBUG static bool RoughlyEqual(const SkPoint& a, const SkPoint& b) { if (approximately_equal(a.fX, b.fX) && approximately_equal(a.fY, b.fY)) { return true; } return RoughlyEqualUlps(a.fX, b.fX) && RoughlyEqualUlps(a.fY, b.fY); } -#endif bool approximatelyPEqual(const SkDPoint& a) const { if (approximately_equal(fX, a.fX) && approximately_equal(fY, a.fY)) { diff --git a/src/pathops/SkPathOpsSimplify.cpp b/src/pathops/SkPathOpsSimplify.cpp index 0917b69e47..57090ac423 100644 --- a/src/pathops/SkPathOpsSimplify.cpp +++ b/src/pathops/SkPathOpsSimplify.cpp @@ -19,9 +19,10 @@ static bool bridgeWinding(SkTArray<SkOpContour*, true>& contourList, SkPathWrite do { int index, endIndex; bool topDone; + bool onlyVertical = false; lastTopLeft = topLeft; SkOpSegment* current = FindSortableTop(contourList, SkOpAngle::kUnaryWinding, &firstContour, - &index, &endIndex, &topLeft, &topUnsortable, &topDone, firstPass); + &index, &endIndex, &topLeft, &topUnsortable, &topDone, &onlyVertical, firstPass); if (!current) { if ((!topUnsortable || firstPass) && !topDone) { SkASSERT(topLeft.fX != SK_ScalarMin && topLeft.fY != SK_ScalarMin); @@ -29,22 +30,21 @@ static bool bridgeWinding(SkTArray<SkOpContour*, true>& contourList, SkPathWrite continue; } break; + } else if (onlyVertical) { + break; } firstPass = !topUnsortable || lastTopLeft != topLeft; - SkTDArray<SkOpSpan*> chaseArray; + SkTDArray<SkOpSpan*> chase; do { if (current->activeWinding(index, endIndex)) { do { if (!unsortable && current->done()) { - if (simple->isEmpty()) { - simple->init(); - break; - } + break; } SkASSERT(unsortable || !current->done()); int nextStart = index; int nextEnd = endIndex; - SkOpSegment* next = current->findNextWinding(&chaseArray, &nextStart, &nextEnd, + SkOpSegment* next = current->findNextWinding(&chase, &nextStart, &nextEnd, &unsortable); if (!next) { if (!unsortable && simple->hasMove() @@ -67,7 +67,7 @@ static bool bridgeWinding(SkTArray<SkOpContour*, true>& contourList, SkPathWrite } while (!simple->isClosed() && (!unsortable || !current->done(SkMin32(index, endIndex)))); if (current->activeWinding(index, endIndex) && !simple->isClosed()) { - SkASSERT(unsortable || simple->isEmpty()); +// SkASSERT(unsortable || simple->isEmpty()); int min = SkMin32(index, endIndex); if (!current->done(min)) { current->addCurveTo(index, endIndex, simple, true); @@ -79,13 +79,17 @@ static bool bridgeWinding(SkTArray<SkOpContour*, true>& contourList, SkPathWrite SkOpSpan* last = current->markAndChaseDoneUnary(index, endIndex); if (last && !last->fChased && !last->fLoop) { last->fChased = true; - SkASSERT(!SkPathOpsDebug::ChaseContains(chaseArray, last)); + SkASSERT(!SkPathOpsDebug::ChaseContains(chase, last)); // assert that last isn't already in array - *chaseArray.append() = last; + *chase.append() = last; +#if DEBUG_WINDING + SkDebugf("%s chase.append id=%d windSum=%d small=%d\n", __FUNCTION__, + last->fOther->span(last->fOtherIndex).fOther->debugID(), last->fWindSum, + last->fSmall); +#endif } } - SkTDArray<SkOpSpan *>* chaseArrayPtr = &chaseArray; - current = FindChase(chaseArrayPtr, &index, &endIndex); + current = FindChase(&chase, &index, &endIndex); #if DEBUG_ACTIVE_SPANS DebugShowActiveSpans(contourList); #endif diff --git a/src/pathops/SkPathOpsTriangle.cpp b/src/pathops/SkPathOpsTriangle.cpp index 7db4831a06..77845e0673 100644 --- a/src/pathops/SkPathOpsTriangle.cpp +++ b/src/pathops/SkPathOpsTriangle.cpp @@ -23,7 +23,7 @@ bool SkDTriangle::contains(const SkDPoint& pt) const { double dot12 = v1.dot(v2); // original code doesn't handle degenerate input; isn't symmetric with inclusion of corner pts; -// introduces necessary error with divide; doesn't short circuit on early answer +// introduces error with divide; doesn't short circuit on early answer #if 0 // Compute barycentric coordinates double invDenom = 1 / (dot00 * dot11 - dot01 * dot01); diff --git a/src/pathops/SkPathOpsTypes.h b/src/pathops/SkPathOpsTypes.h index 4f8bd15344..96627842b3 100644 --- a/src/pathops/SkPathOpsTypes.h +++ b/src/pathops/SkPathOpsTypes.h @@ -91,6 +91,11 @@ const double DBL_EPSILON_ERR = DBL_EPSILON * 4; // FIXME: tune -- allow a few b const double DBL_EPSILON_SUBDIVIDE_ERR = DBL_EPSILON * 16; const double ROUGH_EPSILON = FLT_EPSILON * 64; const double MORE_ROUGH_EPSILON = FLT_EPSILON * 256; +const double WAY_ROUGH_EPSILON = FLT_EPSILON * 2048; + +inline bool zero_or_one(double x) { + return x == 0 || x == 1; +} inline bool approximately_zero(double x) { return fabs(x) < FLT_EPSILON; @@ -297,12 +302,16 @@ inline bool between(double a, double b, double c) { return (a - b) * (c - b) <= 0; } +inline bool roughly_equal(double x, double y) { + return fabs(x - y) < ROUGH_EPSILON; +} + inline bool more_roughly_equal(double x, double y) { return fabs(x - y) < MORE_ROUGH_EPSILON; } -inline bool roughly_equal(double x, double y) { - return fabs(x - y) < ROUGH_EPSILON; +inline bool way_roughly_equal(double x, double y) { + return fabs(x - y) < WAY_ROUGH_EPSILON; } struct SkDPoint; diff --git a/tests/PathOpsAngleIdeas.cpp b/tests/PathOpsAngleIdeas.cpp index 2887b28feb..901cab2bb5 100755 --- a/tests/PathOpsAngleIdeas.cpp +++ b/tests/PathOpsAngleIdeas.cpp @@ -426,7 +426,8 @@ static void testQuadAngles(skiatest::Reporter* reporter, const SkDQuad& quad1, c SkOpSegment seg[2]; makeSegment(quad1, shortQuads[0], &seg[0]); makeSegment(quad2, shortQuads[1], &seg[1]); - int realOverlap = PathOpsAngleTester::ConvexHullOverlaps(seg[0].angle(0), seg[1].angle(0)); + int realOverlap = PathOpsAngleTester::ConvexHullOverlaps(*seg[0].debugLastAngle(), + *seg[1].debugLastAngle()); const SkDPoint& origin = quad1[0]; REPORTER_ASSERT(reporter, origin == quad2[0]); double a1s = atan2(origin.fY - quad1[1].fY, quad1[1].fX - origin.fX); @@ -544,7 +545,8 @@ static void testQuadAngles(skiatest::Reporter* reporter, const SkDQuad& quad1, c } if (overlap < 0) { SkDEBUGCODE(int realEnds =) - PathOpsAngleTester::EndsIntersect(seg[0].angle(0), seg[1].angle(0)); + PathOpsAngleTester::EndsIntersect(*seg[0].debugLastAngle(), + *seg[1].debugLastAngle()); SkASSERT(realEnds == (firstInside ? 1 : 0)); } bruteForce(reporter, quad1, quad2, firstInside); diff --git a/tests/PathOpsAngleTest.cpp b/tests/PathOpsAngleTest.cpp index 1aae27a847..faf61584e6 100644 --- a/tests/PathOpsAngleTest.cpp +++ b/tests/PathOpsAngleTest.cpp @@ -264,7 +264,7 @@ DEF_TEST(PathOpsAngleCircle, reporter) { break; } } - PathOpsAngleTester::Orderable(segment[0].angle(0), segment[1].angle(0)); + PathOpsAngleTester::Orderable(*segment[0].debugLastAngle(), *segment[1].debugLastAngle()); } struct IntersectData { @@ -438,9 +438,9 @@ DEF_TEST(PathOpsAngleAfter, reporter) { } break; } } - SkOpAngle& angle1 = const_cast<SkOpAngle&>(segment[0].angle(0)); - SkOpAngle& angle2 = const_cast<SkOpAngle&>(segment[1].angle(0)); - SkOpAngle& angle3 = const_cast<SkOpAngle&>(segment[2].angle(0)); + SkOpAngle& angle1 = *const_cast<SkOpAngle*>(segment[0].debugLastAngle()); + SkOpAngle& angle2 = *const_cast<SkOpAngle*>(segment[1].debugLastAngle()); + SkOpAngle& angle3 = *const_cast<SkOpAngle*>(segment[2].debugLastAngle()); PathOpsAngleTester::SetNext(angle1, angle3); // These data sets are seeded when the set itself fails, so likely the dataset does not // match the expected result. The tests above return 1 when first added, but diff --git a/tests/PathOpsCubicIntersectionTest.cpp b/tests/PathOpsCubicIntersectionTest.cpp index 17b3f07d3a..b6a9e5910a 100644 --- a/tests/PathOpsCubicIntersectionTest.cpp +++ b/tests/PathOpsCubicIntersectionTest.cpp @@ -162,6 +162,9 @@ static const SkDCubic testSet[] = { const int testSetCount = (int) SK_ARRAY_COUNT(testSet); static const SkDCubic newTestSet[] = { +{{{980.9000244140625, 1474.3280029296875}, {980.9000244140625, 1474.3280029296875}, {978.89300537109375, 1471.95703125}, {981.791015625, 1469.487060546875}}}, +{{{981.791015625, 1469.487060546875}, {981.791015625, 1469.4859619140625}, {983.3580322265625, 1472.72900390625}, {980.9000244140625, 1474.3280029296875}}}, + {{{275,532}, {277.209137,532}, {279,530.209106}, {279,528}}}, {{{278,529}, {278,530.65686}, {276.65686,532}, {275,532}}}, diff --git a/tests/PathOpsDebug.cpp b/tests/PathOpsDebug.cpp index d53271af4d..a2b48acddc 100755 --- a/tests/PathOpsDebug.cpp +++ b/tests/PathOpsDebug.cpp @@ -34,25 +34,69 @@ void SkPathOpsDebug::WindingPrintf(int wind) { #endif void SkOpAngle::dump() const { -#if DEBUG_SORT - debugOne(false); -#endif + dumpOne(true); SkDebugf("\n"); } -void SkOpAngle::dumpFromTo(const SkOpSegment* segment, int from, int to) const { -#if DEBUG_SORT && DEBUG_ANGLE +void SkOpAngle::dumpOne(bool functionHeader) const { +// fSegment->debugValidate(); + const SkOpSpan& mSpan = fSegment->span(SkMin32(fStart, fEnd)); + if (functionHeader) { + SkDebugf("%s ", __FUNCTION__); + } + SkDebugf("[%d", fSegment->debugID()); + SkDebugf("/%d", debugID()); + SkDebugf("] next="); + if (fNext) { + SkDebugf("%d", fNext->fSegment->debugID()); + SkDebugf("/%d", fNext->debugID()); + } else { + SkDebugf("?"); + } + SkDebugf(" sect=%d/%d ", fSectorStart, fSectorEnd); + SkDebugf(" s=%1.9g [%d] e=%1.9g [%d]", fSegment->span(fStart).fT, fStart, + fSegment->span(fEnd).fT, fEnd); + SkDebugf(" sgn=%d windVal=%d", sign(), mSpan.fWindValue); + + SkDebugf(" windSum="); + SkPathOpsDebug::WindingPrintf(mSpan.fWindSum); + if (mSpan.fOppValue != 0 || mSpan.fOppSum != SK_MinS32) { + SkDebugf(" oppVal=%d", mSpan.fOppValue); + SkDebugf(" oppSum="); + SkPathOpsDebug::WindingPrintf(mSpan.fOppSum); + } + if (mSpan.fDone) { + SkDebugf(" done"); + } + if (unorderable()) { + SkDebugf(" unorderable"); + } + if (small()) { + SkDebugf(" small"); + } + if (mSpan.fTiny) { + SkDebugf(" tiny"); + } + if (fSegment->operand()) { + SkDebugf(" operand"); + } + if (fStop) { + SkDebugf(" stop"); + } +} + +void SkOpAngle::dumpTo(const SkOpSegment* segment, const SkOpAngle* to) const { const SkOpAngle* first = this; const SkOpAngle* next = this; const char* indent = ""; do { SkDebugf("%s", indent); - next->debugOne(false); + next->dumpOne(false); if (segment == next->fSegment) { - if (fNext && from == fNext->debugID()) { + if (this == fNext) { SkDebugf(" << from"); } - if (fNext && to == fNext->debugID()) { + if (to == fNext) { SkDebugf(" << to"); } } @@ -60,7 +104,6 @@ void SkOpAngle::dumpFromTo(const SkOpSegment* segment, int from, int to) const { indent = " "; next = next->fNext; } while (next && next != first); -#endif } void SkOpAngle::dumpLoop() const { @@ -81,6 +124,14 @@ void SkOpAngle::dumpPartials() const { } while (next && next != first); } +void SkOpAngleSet::dump() const { + // FIXME: unimplemented +/* This requires access to the internal SkChunkAlloc data + Defer implementing this until it is needed for debugging +*/ + SkASSERT(0); +} + void SkOpContour::dump() const { int segmentCount = fSegments.count(); SkDebugf("((SkOpContour*) 0x%p) [%d]\n", this, debugID()); @@ -99,6 +150,50 @@ void SkOpContour::dumpAngles() const { } } +void SkOpContour::dumpCoincidence(const SkCoincidence& coin) const { + int thisIndex = coin.fSegments[0]; + const SkOpSegment& s1 = fSegments[thisIndex]; + int otherIndex = coin.fSegments[1]; + const SkOpSegment& s2 = coin.fOther->fSegments[otherIndex]; + SkDebugf("((SkOpSegment*) 0x%p) [%d] ((SkOpSegment*) 0x%p) [%d]\n", &s1, s1.debugID(), + &s2, s2.debugID()); + for (int index = 0; index < 2; ++index) { + SkDebugf(" {%1.9gf, %1.9gf}", coin.fPts[0][index].fX, coin.fPts[0][index].fY); + if (coin.fNearly[index]) { + SkDebugf(" {%1.9gf, %1.9gf}", coin.fPts[1][index].fX, coin.fPts[1][index].fY); + } + SkDebugf(" seg1t=%1.9g seg2t=%1.9g\n", coin.fTs[0][index], coin.fTs[1][index]); + } +} + +void SkOpContour::dumpCoincidences() const { + int count = fCoincidences.count(); + if (count > 0) { + SkDebugf("fCoincidences count=%d\n", count); + for (int test = 0; test < count; ++test) { + dumpCoincidence(fCoincidences[test]); + } + } + count = fPartialCoincidences.count(); + if (count == 0) { + return; + } + SkDebugf("fPartialCoincidences count=%d\n", count); + for (int test = 0; test < count; ++test) { + dumpCoincidence(fPartialCoincidences[test]); + } +} + +void SkOpContour::dumpPt(int index) const { + int segmentCount = fSegments.count(); + for (int test = 0; test < segmentCount; ++test) { + const SkOpSegment& segment = fSegments[test]; + if (segment.debugID() == index) { + fSegments[test].dumpPts(); + } + } +} + void SkOpContour::dumpPts() const { int segmentCount = fSegments.count(); SkDebugf("((SkOpContour*) 0x%p) [%d]\n", this, debugID()); @@ -108,6 +203,16 @@ void SkOpContour::dumpPts() const { } } +void SkOpContour::dumpSpan(int index) const { + int segmentCount = fSegments.count(); + for (int test = 0; test < segmentCount; ++test) { + const SkOpSegment& segment = fSegments[test]; + if (segment.debugID() == index) { + fSegments[test].dumpSpans(); + } + } +} + void SkOpContour::dumpSpans() const { int segmentCount = fSegments.count(); SkDebugf("((SkOpContour*) 0x%p) [%d]\n", this, debugID()); @@ -208,25 +313,24 @@ const SkTDArray<SkOpSpan>& SkOpSegment::debugSpans() const { void SkOpSegment::dumpAngles() const { SkDebugf("((SkOpSegment*) 0x%p) [%d]\n", this, debugID()); - int fromIndex = -1, toIndex = -1; + const SkOpAngle* fromAngle = NULL; + const SkOpAngle* toAngle = NULL; for (int index = 0; index < count(); ++index) { - int fIndex = fTs[index].fFromAngleIndex; - int tIndex = fTs[index].fToAngleIndex; - if (fromIndex == fIndex && tIndex == toIndex) { + const SkOpAngle* fAngle = fTs[index].fFromAngle; + const SkOpAngle* tAngle = fTs[index].fToAngle; + if (fromAngle == fAngle && toAngle == tAngle) { continue; } - if (fIndex >= 0) { - SkDebugf(" [%d] from=%d ", index, fIndex); - const SkOpAngle& angle = this->angle(fIndex); - angle.dumpFromTo(this, fIndex, tIndex); + if (fAngle) { + SkDebugf(" [%d] from=%d ", index, fAngle->debugID()); + fAngle->dumpTo(this, tAngle); } - if (tIndex >= 0) { - SkDebugf(" [%d] to=%d ", index, tIndex); - const SkOpAngle& angle = this->angle(tIndex); - angle.dumpFromTo(this, fIndex, tIndex); + if (tAngle) { + SkDebugf(" [%d] to=%d ", index, tAngle->debugID()); + tAngle->dumpTo(this, fAngle); } - fromIndex = fIndex; - toIndex = tIndex; + fromAngle = fAngle; + toAngle = tAngle; } } @@ -279,17 +383,17 @@ void SkOpSegment::dumpSpans() const { } } -void SkPathOpsDebug::DumpAngles(const SkTArray<SkOpAngle, true>& angles) { - int count = angles.count(); +void SkPathOpsDebug::DumpCoincidence(const SkTArray<SkOpContour, true>& contours) { + int count = contours.count(); for (int index = 0; index < count; ++index) { - angles[index].dump(); + contours[index].dumpCoincidences(); } } -void SkPathOpsDebug::DumpAngles(const SkTArray<SkOpAngle* , true>& angles) { - int count = angles.count(); +void SkPathOpsDebug::DumpCoincidence(const SkTArray<SkOpContour* , true>& contours) { + int count = contours.count(); for (int index = 0; index < count; ++index) { - angles[index]->dump(); + contours[index]->dumpCoincidences(); } } @@ -335,6 +439,20 @@ void SkPathOpsDebug::DumpContourPts(const SkTArray<SkOpContour* , true>& contour } } +void SkPathOpsDebug::DumpContourPt(const SkTArray<SkOpContour, true>& contours, int segmentID) { + int count = contours.count(); + for (int index = 0; index < count; ++index) { + contours[index].dumpPt(segmentID); + } +} + +void SkPathOpsDebug::DumpContourPt(const SkTArray<SkOpContour* , true>& contours, int segmentID) { + int count = contours.count(); + for (int index = 0; index < count; ++index) { + contours[index]->dumpPt(segmentID); + } +} + void SkPathOpsDebug::DumpContourSpans(const SkTArray<SkOpContour, true>& contours) { int count = contours.count(); for (int index = 0; index < count; ++index) { @@ -349,6 +467,20 @@ void SkPathOpsDebug::DumpContourSpans(const SkTArray<SkOpContour* , true>& conto } } +void SkPathOpsDebug::DumpContourSpan(const SkTArray<SkOpContour, true>& contours, int segmentID) { + int count = contours.count(); + for (int index = 0; index < count; ++index) { + contours[index].dumpSpan(segmentID); + } +} + +void SkPathOpsDebug::DumpContourSpan(const SkTArray<SkOpContour* , true>& contours, int segmentID) { + int count = contours.count(); + for (int index = 0; index < count; ++index) { + contours[index]->dumpSpan(segmentID); + } +} + void SkPathOpsDebug::DumpSpans(const SkTDArray<SkOpSpan *>& spans) { int count = spans.count(); for (int index = 0; index < count; ++index) { @@ -400,33 +532,45 @@ void SkOpSpan::dumpOne() const { } else { SkDebugf(" other.fID=? [?] otherT=?"); } -#if DEBUG_WINDING - SkDebugf(" windSum="); - SkPathOpsDebug::WindingPrintf(fWindSum); -#endif - if (SkPathOpsDebug::ValidWind(fOppSum) || fOppValue != 0) { -#if DEBUG_WINDING - SkDebugf(" oppSum="); - SkPathOpsDebug::WindingPrintf(fOppSum); -#endif + if (fWindSum != SK_MinS32) { + SkDebugf(" windSum=%d", fWindSum); + } + if (fOppSum != SK_MinS32 && (SkPathOpsDebug::ValidWind(fOppSum) || fOppValue != 0)) { + SkDebugf(" oppSum=%d", fOppSum); } SkDebugf(" windValue=%d", fWindValue); if (SkPathOpsDebug::ValidWind(fOppSum) || fOppValue != 0) { SkDebugf(" oppValue=%d", fOppValue); } - SkDebugf(" from=%d", fFromAngleIndex); - SkDebugf(" to=%d", fToAngleIndex); + if (fFromAngle && fFromAngle->debugID()) { + SkDebugf(" from=%d", fFromAngle->debugID()); + } + if (fToAngle && fToAngle->debugID()) { + SkDebugf(" to=%d", fToAngle->debugID()); + } + if (fChased) { + SkDebugf(" chased"); + } + if (fCoincident) { + SkDebugf(" coincident"); + } if (fDone) { SkDebugf(" done"); } - if (fTiny) { - SkDebugf(" tiny"); + if (fLoop) { + SkDebugf(" loop"); + } + if (fMultiple) { + SkDebugf(" multiple"); + } + if (fNear) { + SkDebugf(" near"); } if (fSmall) { SkDebugf(" small"); } - if (fLoop) { - SkDebugf(" loop"); + if (fTiny) { + SkDebugf(" tiny"); } SkDebugf("\n"); } @@ -444,22 +588,6 @@ void SkOpSpan::dump() const { dumpOne(); } -void Dump(const SkTArray<class SkOpAngle, true>& angles) { - SkPathOpsDebug::DumpAngles(angles); -} - -void Dump(const SkTArray<class SkOpAngle* , true>& angles) { - SkPathOpsDebug::DumpAngles(angles); -} - -void Dump(const SkTArray<class SkOpAngle, true>* angles) { - SkPathOpsDebug::DumpAngles(*angles); -} - -void Dump(const SkTArray<class SkOpAngle* , true>* angles) { - SkPathOpsDebug::DumpAngles(*angles); -} - void Dump(const SkTArray<class SkOpContour, true>& contours) { SkPathOpsDebug::DumpContours(contours); } @@ -476,12 +604,12 @@ void Dump(const SkTArray<class SkOpContour* , true>* contours) { SkPathOpsDebug::DumpContours(*contours); } -void Dump(const SkTDArray<SkOpSpan *>& chaseArray) { - SkPathOpsDebug::DumpSpans(chaseArray); +void Dump(const SkTDArray<SkOpSpan *>& chase) { + SkPathOpsDebug::DumpSpans(chase); } -void Dump(const SkTDArray<SkOpSpan *>* chaseArray) { - SkPathOpsDebug::DumpSpans(*chaseArray); +void Dump(const SkTDArray<SkOpSpan *>* chase) { + SkPathOpsDebug::DumpSpans(*chase); } void DumpAngles(const SkTArray<class SkOpContour, true>& contours) { @@ -500,6 +628,22 @@ void DumpAngles(const SkTArray<class SkOpContour* , true>* contours) { SkPathOpsDebug::DumpContourAngles(*contours); } +void DumpCoin(const SkTArray<class SkOpContour, true>& contours) { + SkPathOpsDebug::DumpCoincidence(contours); +} + +void DumpCoin(const SkTArray<class SkOpContour* , true>& contours) { + SkPathOpsDebug::DumpCoincidence(contours); +} + +void DumpCoin(const SkTArray<class SkOpContour, true>* contours) { + SkPathOpsDebug::DumpCoincidence(*contours); +} + +void DumpCoin(const SkTArray<class SkOpContour* , true>* contours) { + SkPathOpsDebug::DumpCoincidence(*contours); +} + void DumpSpans(const SkTArray<class SkOpContour, true>& contours) { SkPathOpsDebug::DumpContourSpans(contours); } @@ -516,6 +660,22 @@ void DumpSpans(const SkTArray<class SkOpContour* , true>* contours) { SkPathOpsDebug::DumpContourSpans(*contours); } +void DumpSpan(const SkTArray<class SkOpContour, true>& contours, int segmentID) { + SkPathOpsDebug::DumpContourSpan(contours, segmentID); +} + +void DumpSpan(const SkTArray<class SkOpContour* , true>& contours, int segmentID) { + SkPathOpsDebug::DumpContourSpan(contours, segmentID); +} + +void DumpSpan(const SkTArray<class SkOpContour, true>* contours, int segmentID) { + SkPathOpsDebug::DumpContourSpan(*contours, segmentID); +} + +void DumpSpan(const SkTArray<class SkOpContour* , true>* contours, int segmentID) { + SkPathOpsDebug::DumpContourSpan(*contours, segmentID); +} + void DumpPts(const SkTArray<class SkOpContour, true>& contours) { SkPathOpsDebug::DumpContourPts(contours); } @@ -532,6 +692,22 @@ void DumpPts(const SkTArray<class SkOpContour* , true>* contours) { SkPathOpsDebug::DumpContourPts(*contours); } +void DumpPt(const SkTArray<class SkOpContour, true>& contours, int segmentID) { + SkPathOpsDebug::DumpContourPt(contours, segmentID); +} + +void DumpPt(const SkTArray<class SkOpContour* , true>& contours, int segmentID) { + SkPathOpsDebug::DumpContourPt(contours, segmentID); +} + +void DumpPt(const SkTArray<class SkOpContour, true>* contours, int segmentID) { + SkPathOpsDebug::DumpContourPt(*contours, segmentID); +} + +void DumpPt(const SkTArray<class SkOpContour* , true>* contours, int segmentID) { + SkPathOpsDebug::DumpContourPt(*contours, segmentID); +} + static void dumpTestCase(const SkDQuad& quad1, const SkDQuad& quad2, int testNo) { SkDebugf("<div id=\"quad%d\">\n", testNo); quad1.dumpComma(","); diff --git a/tests/PathOpsExtendedTest.cpp b/tests/PathOpsExtendedTest.cpp index 280307a775..fe3d24d6a0 100644 --- a/tests/PathOpsExtendedTest.cpp +++ b/tests/PathOpsExtendedTest.cpp @@ -156,6 +156,11 @@ static void showPathData(const SkPath& path) { while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { switch (verb) { case SkPath::kMove_Verb: + if (firstPtSet && lastPtSet && firstPt != lastPt) { + SkDebugf("{{%1.9g,%1.9g}, {%1.9g,%1.9g}},\n", lastPt.fX, lastPt.fY, + firstPt.fX, firstPt.fY); + lastPtSet = false; + } firstPt = pts[0]; firstPtSet = true; continue; @@ -190,6 +195,10 @@ static void showPathData(const SkPath& path) { return; } } + if (firstPtSet && lastPtSet && firstPt != lastPt) { + SkDebugf("{{%1.9g,%1.9g}, {%1.9g,%1.9g}},\n", lastPt.fX, lastPt.fY, + firstPt.fX, firstPt.fY); + } } #endif @@ -410,7 +419,6 @@ static void showPathOpPath(const char* testName, const SkPath& one, const SkPath SkDebugf("static void %s(skiatest::Reporter* reporter, const char* filename) {\n", testName); *gTestOp.append() = shapeOp; ++gTestNo; - SkDebugf("\n*** this test fails ***\n"); SkDebugf(" SkPath path, pathB;\n"); showPath(a, "path", false); showPath(b, "pathB", false); @@ -440,6 +448,7 @@ static int comparePaths(skiatest::Reporter* reporter, const char* testName, cons if (errors2x2 > MAX_ERRORS && gComparePathsAssert) { SK_DECLARE_STATIC_MUTEX(compareDebugOut3); SkAutoMutexAcquire autoM(compareDebugOut3); + SkDebugf("\n*** this test fails ***\n"); showPathOpPath(testName, one, two, a, b, scaledOne, scaledTwo, shapeOp, scale); REPORTER_ASSERT(reporter, 0); } else if (gShowPath || errors2x2 == MAX_ERRORS || errors2x2 == MAX_ERRORS - 1) { diff --git a/tests/PathOpsLineIntersectionTest.cpp b/tests/PathOpsLineIntersectionTest.cpp index 9885178603..379c2f16f9 100644 --- a/tests/PathOpsLineIntersectionTest.cpp +++ b/tests/PathOpsLineIntersectionTest.cpp @@ -50,7 +50,10 @@ static const SkDLine noIntersect[][2] = { static const size_t noIntersect_count = SK_ARRAY_COUNT(noIntersect); static const SkDLine coincidentTests[][2] = { - {{{{0,482.5}, {-4.4408921e-016,682.5}}}, + {{{ { 10105, 2510 }, { 10123, 2509.98999f } }}, + {{{10105, 2509.98999f}, { 10123, 2510 } }}}, + + {{ { { 0, 482.5 }, { -4.4408921e-016, 682.5 } } }, {{{0,683}, {0,482}}}}, {{{{1.77635684e-015,312}, {-1.24344979e-014,348}}}, @@ -76,9 +79,12 @@ static void check_results(skiatest::Reporter* reporter, const SkDLine& line1, co for (int i = 0; i < ts.used(); ++i) { SkDPoint result1 = line1.ptAtT(ts[0][i]); SkDPoint result2 = line2.ptAtT(ts[1][i]); - if (!result1.approximatelyEqual(result2)) { + if (!result1.approximatelyEqual(result2) && !ts.nearlySame(i)) { REPORTER_ASSERT(reporter, ts.used() != 1); result2 = line2.ptAtT(ts[1][i ^ 1]); + if (!result1.approximatelyEqual(result2)) { + SkDebugf("."); + } REPORTER_ASSERT(reporter, result1.approximatelyEqual(result2)); REPORTER_ASSERT(reporter, result1.approximatelyEqual(ts.pt(i).asSkPoint())); } diff --git a/tests/PathOpsOpTest.cpp b/tests/PathOpsOpTest.cpp index 75b6030aa5..5317792922 100644 --- a/tests/PathOpsOpTest.cpp +++ b/tests/PathOpsOpTest.cpp @@ -2065,6 +2065,11 @@ static void rectOp3x(skiatest::Reporter* reporter, const char* filename) { testPathOp(reporter, path, pathB, kXOR_PathOp, filename); } +#define ISSUE_1435_FIXED 0 +#if ISSUE_1435_FIXED +// this fails to generate two interior line segments +// an earlier pathops succeeded, but still failed to generate one interior line segment +// (but was saved by assemble, which works around a single line missing segment) static void issue1435(skiatest::Reporter* reporter, const char* filename) { SkPath path1; path1.moveTo(160, 60); @@ -2115,6 +2120,7 @@ static void issue1435(skiatest::Reporter* reporter, const char* filename) { path2.setFillType(SkPath::kEvenOdd_FillType); testPathOp(reporter, path1, path2, kIntersect_PathOp, filename); } +#endif static void skpkkiste_to716(skiatest::Reporter* reporter, const char* filename) { SkPath path; @@ -3348,10 +3354,116 @@ static void issue2504(skiatest::Reporter* reporter, const char* filename) { testPathOp(reporter, path1, path2, kUnion_PathOp, filename); } +#define TEST_2540 0 +#if TEST_2540 // FIXME: extends cubic arm for sorting, marks extension with wrong winding? +static void issue2540(skiatest::Reporter* reporter, const char* filename) { + SkPath path1; + path1.moveTo(26.5054988861083984375, 85.73960113525390625); + path1.cubicTo(84.19739532470703125, 17.77140045166015625, 16.93920135498046875, 101.86199951171875, 12.631000518798828125, 105.24700164794921875); + path1.cubicTo(11.0819997787475585937500000, 106.46399688720703125, 11.5260000228881835937500000, 104.464996337890625, 11.5260000228881835937500000, 104.464996337890625); + path1.lineTo(23.1654987335205078125, 89.72879791259765625); + path1.cubicTo(23.1654987335205078125, 89.72879791259765625, -10.1713008880615234375, 119.9160003662109375, -17.1620006561279296875, 120.8249969482421875); + path1.cubicTo(-19.1149997711181640625, 121.07900238037109375, -18.0380001068115234375, 119.79299163818359375, -18.0380001068115234375, 119.79299163818359375); + path1.cubicTo(-18.0380001068115234375, 119.79299163818359375, 14.22100067138671875, 90.60700225830078125, 26.5054988861083984375, 85.73960113525390625); + path1.close(); + + SkPath path2; + path2.moveTo(-25.077999114990234375, 124.9120025634765625); + path2.cubicTo(-25.077999114990234375, 124.9120025634765625, -25.9509983062744140625, 125.95400238037109375, -24.368999481201171875, 125.7480010986328125); + path2.cubicTo(-16.06999969482421875, 124.66899871826171875, 1.2680000066757202148437500, 91.23999786376953125, 37.264003753662109375, 95.35400390625); + path2.cubicTo(37.264003753662109375, 95.35400390625, 11.3710002899169921875, 83.7339935302734375, -25.077999114990234375, 124.9120025634765625); + path2.close(); + testPathOp(reporter, path1, path2, kUnion_PathOp, filename); +} +#endif + +static void rects1(skiatest::Reporter* reporter, const char* filename) { + SkPath path, pathB; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(0, 0); + path.lineTo(1, 0); + path.lineTo(1, 1); + path.lineTo(0, 1); + path.close(); + path.moveTo(0, 0); + path.lineTo(6, 0); + path.lineTo(6, 6); + path.lineTo(0, 6); + path.close(); + pathB.setFillType(SkPath::kEvenOdd_FillType); + pathB.moveTo(0, 0); + pathB.lineTo(1, 0); + pathB.lineTo(1, 1); + pathB.lineTo(0, 1); + pathB.close(); + pathB.moveTo(0, 0); + pathB.lineTo(2, 0); + pathB.lineTo(2, 2); + pathB.lineTo(0, 2); + pathB.close(); + testPathOp(reporter, path, pathB, kUnion_PathOp, filename); +} + +static void rects2(skiatest::Reporter* reporter, const char* filename) { + SkPath path, pathB; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(0, 0); + path.lineTo(4, 0); + path.lineTo(4, 4); + path.lineTo(0, 4); + path.close(); + path.moveTo(3, 3); + path.lineTo(4, 3); + path.lineTo(4, 4); + path.lineTo(3, 4); + path.close(); + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(3, 3); + pathB.lineTo(6, 3); + pathB.lineTo(6, 6); + pathB.lineTo(3, 6); + pathB.close(); + pathB.moveTo(3, 3); + pathB.lineTo(4, 3); + pathB.lineTo(4, 4); + pathB.lineTo(3, 4); + pathB.close(); + testPathOp(reporter, path, pathB, kDifference_PathOp, filename); +} + +static void rects3(skiatest::Reporter* reporter, const char* filename) { + SkPath path, pathB; + path.setFillType(SkPath::kEvenOdd_FillType); + path.addRect(0, 0, 1, 1, SkPath::kCW_Direction); + path.addRect(0, 0, 4, 4, SkPath::kCW_Direction); + pathB.setFillType(SkPath::kWinding_FillType); + pathB.addRect(0, 0, 2, 2, SkPath::kCW_Direction); + pathB.addRect(0, 0, 2, 2, SkPath::kCW_Direction); + testPathOp(reporter, path, pathB, kDifference_PathOp, filename); +} + +static void rects4(skiatest::Reporter* reporter, const char* filename) { + SkPath path, pathB; + path.setFillType(SkPath::kEvenOdd_FillType); + path.addRect(0, 0, 1, 1, SkPath::kCW_Direction); + path.addRect(0, 0, 2, 2, SkPath::kCW_Direction); + pathB.setFillType(SkPath::kWinding_FillType); + pathB.addRect(0, 0, 2, 2, SkPath::kCW_Direction); + pathB.addRect(0, 0, 3, 3, SkPath::kCW_Direction); + testPathOp(reporter, path, pathB, kDifference_PathOp, filename); +} + static void (*firstTest)(skiatest::Reporter* , const char* filename) = 0; static void (*stopTest)(skiatest::Reporter* , const char* filename) = 0; static struct TestDesc tests[] = { + TEST(rects4), + TEST(rects3), + TEST(rects2), + TEST(rects1), +#if TEST_2540 // FIXME: extends cubic arm for sorting, marks extension with wrong winding? + TEST(issue2540), +#endif TEST(issue2504), TEST(kari1), TEST(quadOp10i), @@ -3390,7 +3502,9 @@ static struct TestDesc tests[] = { TEST(cubicOp101), TEST(cubicOp100), TEST(cubicOp99), +#if ISSUE_1435_FIXED TEST(issue1435), +#endif TEST(cubicOp98x), TEST(cubicOp97x), TEST(skpcarpetplanet_ru22), // cubic/cubic intersect detects unwanted coincidence diff --git a/tests/PathOpsSimplifyTest.cpp b/tests/PathOpsSimplifyTest.cpp index 7b5128cc2b..4bfab14488 100644 --- a/tests/PathOpsSimplifyTest.cpp +++ b/tests/PathOpsSimplifyTest.cpp @@ -4653,9 +4653,26 @@ static void testQuads61(skiatest::Reporter* reporter, const char* filename) { testSimplify(reporter, path, filename); } -static void (*firstTest)(skiatest::Reporter* , const char* filename) = testQuadratic56; +static void testQuadralateral10(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kWinding_FillType); + path.moveTo(0, 0); + path.lineTo(0, 0); + path.lineTo(0, 0); + path.lineTo(2, 2); + path.close(); + path.moveTo(1, 0); + path.lineTo(1, 1); + path.lineTo(2, 2); + path.lineTo(1, 3); + path.close(); + testSimplify(reporter, path, filename); +} + +static void (*firstTest)(skiatest::Reporter* , const char* filename) = 0; static TestDesc tests[] = { + TEST(testQuadralateral10), TEST(testQuads61), TEST(testQuads60), TEST(testQuads59), diff --git a/tests/PathOpsSkpClipTest.cpp b/tests/PathOpsSkpClipTest.cpp index c0f028c55f..da505451da 100755 --- a/tests/PathOpsSkpClipTest.cpp +++ b/tests/PathOpsSkpClipTest.cpp @@ -27,87 +27,197 @@ #else #define PATH_SLASH "/" #define IN_DIR "/skp/2311328-7fc2228/slave" - #define OUT_DIR "/skpOut/2/" + #define OUT_DIR "/skpOut/4/" #endif const struct { int directory; const char* filename; } skipOverSept[] = { - { 9, "http___www_catingueiraonline_com_.skp"}, // infinite loop - {13, "http___www_galaxystwo_com_.skp"}, // infinite loop - {15, "http___www_giffingtool_com_.skp"}, // joinCoincidence / findT / assert - {15, "http___www_thaienews_blogspot_com_.skp"}, // infinite loop - {17, "http___www_gruposejaumdivulgador_com_br_.skp"}, // calcCoincidentWinding asserts zeroSpan + { 3, "http___www_americascup_com_.skp"}, // !simple->closed() {18, "http___www_argus_presse_fr_.skp"}, // can't find winding of remaining vertical edge - {21, "http___www_fashionscandal_com_.skp"}, // infinite loop - {21, "http___www_kenlevine_blogspot_com_.skp"}, // infinite loop - {25, "http___www_defense_studies_blogspot_com_.skp"}, // infinite loop - {27, "http___www_brokeroutpost_com_.skp"}, // suspect infinite loop - {28, "http___www_jaimebatistadasilva_blogspot_com_br_.skp"}, // suspect infinite loop - {28, "http___www_odia_com_br_.skp"}, // !simple->isClosed() - {29, "http___www_hubbyscook_com_.skp"}, // joinCoincidence / findT / assert - {30, "http___www_spankystokes_com_.skp"}, // suspect infinite loop - {32, "http___www_adalbertoday_blogspot_com_br_.skp"}, // suspect infinite loop - {32, "http___www_galery_annisa_com_.skp"}, // suspect infinite loop - {33, "http___www_pindosiya_com_.skp"}, // line quad intersection SkIntersections::assert - {36, "http___www_educationalcraft_com_.skp"}, // cubic / cubic near end / assert in SkIntersections::insert (missing skp test) - {36, "http___www_shaam_org_.skp"}, // suspect infinite loop - {36, "http___www_my_pillow_book_blogspot_gr_.skp"}, // suspect infinite loop - {39, "http___www_opbeat_com_.skp"}, // suspect infinite loop - {40, "http___www_phototransferapp_com_.skp"}, // !simple->isClosed() - {41, "http___www_freeismylife_com_.skp"}, // suspect infinite loop - {41, "http___www_accordidelmomento_com_.skp"}, // suspect infinite loop - {41, "http___www_evolvehq_com_.skp"}, // joinCoincidence / findT / assert - {44, "http___www_contextualnewsfeeds_com_.skp"}, // !simple->isClosed() + {31, "http___www_narayana_verlag_de_.skp"}, // !simple->closed() + {36, "http___www_educationalcraft_com_.skp"}, // cubic / cubic near end / assert in SkIntersections::insert {44, "http___www_cooksnaps_com_.skp"}, // !simple->isClosed() - {44, "http___www_helha_be_.skp"}, // !simple->isClosed() - {45, "http___www_blondesmakemoney_blogspot_com_.skp"}, // suspect infinite loop - {46, "http___www_cheaphealthygood_blogspot_com_.skp"}, // suspect infinite loop - {47, "http___www_ajitvadakayil_blogspot_in_.skp"}, // suspect infinite loop - {49, "http___www_karnivool_com_au_.skp"}, // SkOpAngle::setSector SkASSERT(fSectorStart >= 0); - {49, "http___www_tunero_de_.skp"}, // computeonesumreverse calls markwinding with 0 winding - {49, "http___www_thaienews_blogspot_sg_.skp"}, // suspect infinite loop - {50, "http___www_docgelo_com_.skp"}, // rightAngleWinding (probably same as argus_presse) + {48, "http___www_narayana_publishers_com_.skp"}, // !simple->isClosed() + {51, "http___www_freedominthe50states_org_.skp"}, // corrupt dash data + {52, "http___www_aceinfographics_com_.skp"}, // right angle winding assert {53, "http___www_lojaanabotafogo_com_br_.skp"}, // rrect validate assert - {54, "http___www_odecktestanswer2013_blogspot_in_.skp"}, // suspect infinite loop - {54, "http___www_cleristonsilva_com_br_.skp"}, // suspect infinite loop - {56, "http___www_simplysaru_com_.skp"}, // joinCoincidence / findT / assert - {57, "http___www_koukfamily_blogspot_gr_.skp"}, // suspect infinite loop - {57, "http___www_dinar2010_blogspot_com_.skp"}, // suspect infinite loop - {58, "http___www_artblart_com_.skp"}, // rightAngleWinding - {59, "http___www_accrispin_blogspot_com_.skp"}, // suspect infinite loop - {59, "http___www_vicisitudysordidez_blogspot_com_es_.skp"}, // suspect infinite loop - {60, "http___www_thehousingbubbleblog_com_.skp"}, // suspect infinite loop - {61, "http___www_jessicaslens_wordpress_com_.skp"}, // joinCoincidence / findT / assert - {61, "http___www_partsdata_de_.skp"}, // cubic-cubic intersection reduce checkLinear assert - {62, "http___www_blondesmakemoney_blogspot_com_au_.skp"}, // suspect infinite loop - {62, "http___www_intellibriefs_blogspot_in_.skp"}, // suspect infinite loop - {63, "http___www_tankerenemy_com_.skp"}, // suspect infinite loop - {65, "http___www_kpopexplorer_net_.skp"}, // joinCoincidence / findT / assert - {65, "http___www_bestthingsinbeauty_blogspot_com_.skp"}, // suspect infinite loop - {65, "http___www_wartepop_blogspot_com_br_.skp"}, // !simple->isClosed() - {65, "http___www_eolake_blogspot_com_.skp"}, // suspect infinite loop - {67, "http___www_cacadordemisterio_blogspot_com_br_.skp"}, // suspect infinite loop - {69, "http___www_misnotasyapuntes_blogspot_mx_.skp"}, // suspect infinite loop - {69, "http___www_awalkintheparknyc_blogspot_com_.skp"}, // suspect infinite loop - {71, "http___www_lokado_de_.skp"}, // joinCoincidence / findT / assert - {72, "http___www_karlosdesanjuan_blogspot_com_.skp"}, // suspect infinite loop - {73, "http___www_cyberlawsinindia_blogspot_in_.skp"}, // suspect infinite loop - {73, "http___www_taxiemmovimento_blogspot_com_br_.skp"}, // suspect infinite loop - {74, "http___www_giveusliberty1776_blogspot_com_.skp"}, // suspect infinite loop - {75, "http___www_e_cynical_blogspot_gr_.skp"}, // suspect infinite loop - {76, "http___www_seopack_blogspot_com_.skp"}, // SkOpAngle::setSector SkASSERT(fSectorStart >= 0); - {77, "http___www_sunsky_russia_com_.skp"}, // joinCoincidence / findT / assert (no op test, already fixed hopefully) - {78, "http___www_bisnisonlineinfo_com_.skp"}, // suspect infinite loop - {79, "http___www_danielsgroupcpa_com_.skp"}, // joinCoincidence / findT / assert (no op test, already fixed hopefully) - {80, "http___www_clinique_portugal_com_.skp"}, // suspect infinite loop - {81, "http___www_europebusines_blogspot_com_.skp"}, // suspect infinite loop - {82, "http___www_apopsignomi_blogspot_gr_.skp"}, // suspect infinite loop - {85, "http___www_ajitvadakayil_blogspot_com_.skp"}, // suspect infinite loop - {86, "http___www_madhousefamilyreviews_blogspot_co_uk_.skp"}, // suspect infinite loop + {57, "http___www_vantageproduction_com_.skp"}, // !isClosed() + {64, "http___www_etiqadd_com_.skp"}, // !simple->closed() + {84, "http___www_swapspacesystems_com_.skp"}, // !simple->closed() + {90, "http___www_tcmevents_org_.skp"}, // !simple->closed() + {96, "http___www_paseoitaigara_com_br_.skp"}, // !simple->closed() + {98, "http___www_mortgagemarketguide_com_.skp"}, // !simple->closed() + {99, "http___www_kitcheninspirations_wordpress_com_.skp"}, // checkSmall / bumpSpan }; +/* stats +97 http___www_brandyandvinca_com_.skp pixelError=3 +95 http___www_into_asia_com_.skp pixelError=12 +93 http___www_lunarplanner_com_.skp pixelError=14 +98 http___www_lovelyitalia_com_.skp pixelError=17 +90 http___www_inter_partner_blogspot_com_.skp pixelError=18 +99 http___www_maxarea_com_.skp pixelError=26 +98 http___www_maroonsnet_org_.skp pixelError=33 +92 http___www_belinaart_ru_.skp pixelError=50 +100 http___www_chroot_ro_.skp pixelError=62 +99 http___www_hsbrands_com_.skp pixelError=98 +95 http___www_tournamentindicator_com_.skp pixelError=122 +93 http___www_businesses_com_au_.skp pixelError=162 +90 http___www_regenesys_net_.skp pixelError=182 +88 http___www_1863544208148625103_c18eac63985503fa85b06358959c1ba27fc36f82_blogspot_com_.skp pixelError=186 +97 http___www_pregacoesevangelica_com_br_.skp pixelError=240 +77 http___www_zhenggang_org_.skp pixelError=284 +96 http___slidesharemailer_com_.skp pixelError=522 +94 http___www_gensteel_com_.skp pixelError=555 +68 http___www_jf_eti_br_.skp pixelError=610 +83 http___www_swishiat_com_.skp pixelError=706 +96 http___www_matusikmissive_com_au_.skp pixelError=2580 +95 http___www_momentumnation_com_.skp pixelError=3938 +92 http___www_rssowl_com_.skp pixelError=5113 +96 http___www_sexxygirl_tv_.skp pixelError=7605 +99 http___www_georgevalah_wordpress_com_.skp pixelError=8386 +78 http___www_furbo_org_.skp pixelError=8656 +78 http___www_djxhemary_wordpress_com_.skp pixelError=8976 +100 http___www_mindcontrolblackassassins_com_.skp pixelError=31950 +98 http___bababillgates_free_fr_.skp pixelError=40237 +98 http___hepatite_ro_.skp pixelError=44370 +86 http___www_somethingwagging_com_.skp pixelError=47794 +84 http___www_beverageuniverse_com_.skp pixelError=65450 +50 http___www_aveksa_com_.skp pixelError=68194 +10 http___www_publiker_pl_.skp pixelError=89997 +61 http___www_dominos_co_id_.skp pixelError=476868 +87 http___www_du_edu_om_.skp time=46 +87 http___www_bigload_de_.skp time=46 +100 http___www_home_forum_com_.skp time=48 +97 http___www_hotamateurchat_com_.skp time=48 +97 http___www_myrsky_com_cn_.skp time=48 +98 http___www_techiegeex_com_.skp time=49 +82 http___www_fashionoutletsofchicago_com_.skp time=50 +77 http___www_dynamischbureau_nl_.skp time=50 +82 http___www_mayihelpu_co_in_.skp time=50 +84 http___www_vbox7_com_user_history_viewers_.skp time=50 +85 http___www_ktokogda_com_.skp time=50 +85 http___www_propertyturkeysale_com_.skp time=50 +85 http___www_51play_com_.skp time=50 +86 http___www_bayalarm_com_.skp time=50 +87 http___www_eaglepictures_com_.skp time=50 +88 http___www_atlasakvaryum_com_.skp time=50 +91 http___www_pioneerchryslerjeep_com_.skp time=50 +94 http___www_thepulsemag_com_.skp time=50 +95 http___www_dcshoes_com_ph_.skp time=50 +96 http___www_montrealmassage_ca_.skp time=50 +96 http___www_jkshahclasses_com_.skp time=50 +96 http___www_webcamconsult_com_.skp time=51 +100 http___www_bsoscblog_com_.skp time=52 +95 http___www_flaktwoods_com_.skp time=53 +91 http___www_qivivo_com_.skp time=54 +90 http___www_unitender_com_.skp time=56 +97 http___www_casinogaming_com_.skp time=56 +97 http___www_rootdownload_com_.skp time=56 +94 http___www_aspa_ev_de_.skp time=57 +98 http___www_tenpieknyswiat_pl_.skp time=57 +93 http___www_transocean_de_.skp time=58 +94 http___www_vdo2_blogspot_com_.skp time=58 +94 http___www_asmaissexy_com_br_.skp time=58 +100 http___www_prefeiturasjm_com_br_.skp time=60 +100 http___www_eduinsuranceclick_blogspot_com_.skp time=60 +96 http___www_bobdunsire_com_.skp time=61 +96 http___www_omgkettlecorn_com_.skp time=61 +85 http___www_fbbsessions_com_.skp time=62 +86 http___www_hector_ru_.skp time=62 +87 http___www_wereldsupporter_nl_.skp time=62 +90 http___www_arello_com_.skp time=62 +93 http___www_bayerplastics_com_.skp time=62 +93 http___www_superandolamovida_com_ar_.skp time=62 +96 http___www_med_rbf_ru_.skp time=62 +81 http___www_carnegiescience_edu_.skp time=65 +87 http___www_asanewengland_com_.skp time=65 +92 http___www_turkce_karakter_appspot_com_.skp time=65 +94 http___www_k3a_org_.skp time=65 +96 http___www_powermaccenter_com_.skp time=65 +98 http___www_avto49_ru_.skp time=67 +100 http___www_hetoldeambaecht_nl_.skp time=68 +95 http___www_marine_ie_.skp time=69 +96 http___www_quebecvapeboutique_com_.skp time=69 +95 http___www_brays_ingles_com_.skp time=70 +100 http___www_lacondesa_com_.skp time=72 +95 http___www_timbarrathai_com_au_.skp time=76 +95 http___www_cuissedegrenouille_com_.skp time=76 +95 http___www_iwama51_ru_.skp time=76 +99 http___www_fotoantologia_it_.skp time=76 +92 http___www_indian_architects_com_.skp time=78 +92 http___www_totalwomanspa_com_.skp time=78 +100 http___www_fachverband_spielhallen_de_.skp time=83 +93 http___www_golshanemehr_ir_.skp time=84 +95 http___www_maryesses_com_.skp time=84 +99 http___www_ddcorp_ca_.skp time=89 +90 http___www_brontops_com_.skp time=89 +94 http___www_robgolding_com_.skp time=89 +91 http___www_tecban_com_br_.skp time=91 +98 http___www_costamesakarate_com_.skp time=100 +95 http___www_monsexyblog_com_.skp time=103 +97 http___www_stornowaygazette_co_uk_.skp time=103 +93 http___www_fitforaframe_com_.skp time=104 +98 http___www_intentionoftheday_com_.skp time=113 +100 http___www_tailgateclothing_com_.skp time=117 +95 http___www_senbros_com_.skp time=118 +93 http___www_lettoblog_com_.skp time=121 +94 http___www_maxineschallenge_com_au_.skp time=125 +95 http___www_savvycard_net_.skp time=127 +95 http___www_open_ac_mu_.skp time=129 +96 http___www_avgindia_in_.skp time=135 +97 http___www_stocktonseaview_com_.skp time=135 +96 http___www_distroller_com_.skp time=142 +94 http___www_travoggalop_dk_.skp time=144 +100 http___www_history_im_.skp time=144 +94 http___www_playradio_sk_.skp time=145 +92 http___www_linglongglass_com_.skp time=151 +97 http___www_bizzna_com_.skp time=151 +96 http___www_spiros_ws_.skp time=154 +91 http___www_rosen_meents_co_il_.skp time=156 +81 http___www_hoteldeluxeportland_com_.skp time=158 +92 http___www_freetennis_org_.skp time=161 +93 http___www_aircharternetwork_com_au_.skp time=161 +94 http___www_austinparks_org_.skp time=165 +89 http___www_bevvy_co_.skp time=168 +91 http___www_sosyalhile_net_.skp time=168 +98 http___www_minvih_gob_ve_.skp time=171 +89 http___www_streetfoodmtl_com_.skp time=172 +92 http___www_loveslatinas_tumblr_com_.skp time=178 +93 http___www_madbites_co_in_.skp time=180 +94 http___www_rocktarah_ir_.skp time=185 +97 http___www_penthouselife_com_.skp time=185 +96 http___www_appymonkey_com_.skp time=196 +92 http___www_pasargadhotels_com_.skp time=203 +99 http___www_marina_mil_pe_.skp time=203 +89 http___www_kays_co_uk_.skp time=205 +77 http___www_334588_com_.skp time=211 +83 http___www_trendbad24_de_.skp time=211 +81 http___www_cdnetworks_co_kr_.skp time=216 +94 http___www_schellgames_com_.skp time=223 +95 http___www_juliaweddingnews_cn_.skp time=230 +92 http___www_xcrafters_pl_.skp time=253 +93 http___www_pondoo_com_.skp time=253 +96 http___www_helsinkicapitalpartners_fi_.skp time=255 +88 http___www_nadtexican_com_.skp time=259 +85 http___www_canstockphoto_hu_.skp time=266 +78 http___www_ecovacs_com_cn_.skp time=271 +93 http___www_brookfieldplaceny_com_.skp time=334 +93 http___www_fmastrengthtraining_com_.skp time=337 +94 http___www_turtleonthebeach_com_.skp time=394 +90 http___www_temptationthemovie_com_.skp time=413 +95 http___www_patongsawaddi_com_.skp time=491 +91 http___www_online_radio_appspot_com_.skp time=511 +68 http___www_richardmiller_co_uk_.skp time=528 +63 http___www_eschrade_com_.skp time=543 +55 http___www_interaction_inf_br_.skp time=625 +38 http___www_huskyliners_com_.skp time=632 +86 http___granda_net_.skp time=1067 +24 http___www_cocacolafm_com_br_.skp time=1081 +*/ + size_t skipOverSeptCount = sizeof(skipOverSept) / sizeof(skipOverSept[0]); enum TestStep { @@ -116,7 +226,7 @@ enum TestStep { }; enum { - kMaxLength = 128, + kMaxLength = 256, kMaxFiles = 128, kSmallLimit = 1000, }; @@ -190,6 +300,13 @@ public: } }; +class SortByName : public TestResult { +public: + bool operator<(const SortByName& rh) const { + return strcmp(fFilename, rh.fFilename) < 0; + } +}; + struct TestState { void init(int dirNo, skiatest::Reporter* reporter) { fReporter = reporter; @@ -217,11 +334,6 @@ struct TestRunner { class TestRunnable : public SkRunnable { public: - TestRunnable(void (*testFun)(TestState*), int dirNo, TestRunner* runner) { - fState.init(dirNo, runner->fReporter); - fTestFun = testFun; - } - virtual void run() SK_OVERRIDE { SkGraphics::SetTLSFontCacheLimit(1 * 1024 * 1024); (*fTestFun)(&fState); @@ -231,6 +343,33 @@ public: void (*fTestFun)(TestState*); }; + +class TestRunnableDir : public TestRunnable { +public: + TestRunnableDir(void (*testFun)(TestState*), int dirNo, TestRunner* runner) { + fState.init(dirNo, runner->fReporter); + fTestFun = testFun; + } + +}; + +class TestRunnableFile : public TestRunnable { +public: + TestRunnableFile(void (*testFun)(TestState*), int dirNo, const char* name, TestRunner* runner) { + fState.init(dirNo, runner->fReporter); + strcpy(fState.fResult.fFilename, name); + fTestFun = testFun; + } +}; + +class TestRunnableEncode : public TestRunnableFile { +public: + TestRunnableEncode(void (*testFun)(TestState*), int dirNo, const char* name, TestRunner* runner) + : TestRunnableFile(testFun, dirNo, name, runner) { + fState.fResult.fTestStep = kEncodeFiles; + } +}; + TestRunner::~TestRunner() { for (int index = 0; index < fRunnables.count(); index++) { SkDELETE(fRunnables[index]); @@ -272,6 +411,16 @@ static SkString make_in_dir_name(int dirNo) { return dirName; } +static SkString make_stat_dir_name(int dirNo) { + SkString dirName(outStatusDir); + dirName.appendf("%d", dirNo); + if (!sk_exists(dirName.c_str())) { + SkDebugf("could not read dir %s\n", dirName.c_str()); + return SkString(); + } + return dirName; +} + static bool make_one_out_dir(const char* outDirStr) { SkString outDir = make_filepath(0, outDirStr, ""); if (!sk_exists(outDir.c_str())) { @@ -675,32 +824,79 @@ static bool initTest() { return make_out_dirs(); } +static bool initUberTest(int firstDirNo, int lastDirNo) { + if (!initTest()) { + return false; + } + for (int index = firstDirNo; index <= lastDirNo; ++index) { + SkString statusDir(outStatusDir); + statusDir.appendf("%d", index); + if (!make_one_out_dir(statusDir.c_str())) { + return false; + } + } + return true; +} + + +static void testSkpClipEncode(TestState* data) { + data->fResult.testOne(); + if (data->fReporter->verbose()) { + SkDebugf("+"); + } +} + static void encodeFound(skiatest::Reporter* reporter, TestState& state) { if (reporter->verbose()) { - SkTDArray<SortByPixel*> worst; - for (int index = 0; index < state.fPixelWorst.count(); ++index) { - *worst.append() = &state.fPixelWorst[index]; - } - SkTQSort<SortByPixel>(worst.begin(), worst.end() - 1); - for (int index = 0; index < state.fPixelWorst.count(); ++index) { - const TestResult& result = *worst[index]; - SkDebugf("%d %s pixelError=%d\n", result.fDirNo, result.fFilename, result.fPixelError); + if (state.fPixelWorst.count()) { + SkTDArray<SortByPixel*> worst; + for (int index = 0; index < state.fPixelWorst.count(); ++index) { + *worst.append() = &state.fPixelWorst[index]; + } + SkTQSort<SortByPixel>(worst.begin(), worst.end() - 1); + for (int index = 0; index < state.fPixelWorst.count(); ++index) { + const TestResult& result = *worst[index]; + SkDebugf("%d %s pixelError=%d\n", result.fDirNo, result.fFilename, result.fPixelError); + } } - SkTDArray<SortByTime*> slowest; - for (int index = 0; index < state.fSlowest.count(); ++index) { - *slowest.append() = &state.fSlowest[index]; + if (state.fSlowest.count()) { + SkTDArray<SortByTime*> slowest; + for (int index = 0; index < state.fSlowest.count(); ++index) { + *slowest.append() = &state.fSlowest[index]; + } + if (slowest.count() > 0) { + SkTQSort<SortByTime>(slowest.begin(), slowest.end() - 1); + for (int index = 0; index < slowest.count(); ++index) { + const TestResult& result = *slowest[index]; + SkDebugf("%d %s time=%d\n", result.fDirNo, result.fFilename, result.fTime); + } + } } - SkTQSort<SortByTime>(slowest.begin(), slowest.end() - 1); - for (int index = 0; index < slowest.count(); ++index) { - const TestResult& result = *slowest[index]; - SkDebugf("%d %s time=%d\n", result.fDirNo, result.fFilename, result.fTime); + } + + int threadCount = reporter->allowThreaded() ? SkThreadPool::kThreadPerCore : 1; + TestRunner testRunner(reporter, threadCount); + for (int index = 0; index < state.fPixelWorst.count(); ++index) { + const TestResult& result = state.fPixelWorst[index]; + SkString filename(result.fFilename); + if (!filename.endsWith(".skp")) { + filename.append(".skp"); } + *testRunner.fRunnables.append() = SkNEW_ARGS(TestRunnableEncode, + (&testSkpClipEncode, result.fDirNo, filename.c_str(), &testRunner)); } + testRunner.render(); +#if 0 for (int index = 0; index < state.fPixelWorst.count(); ++index) { const TestResult& result = state.fPixelWorst[index]; - TestResult::Test(result.fDirNo, result.fFilename, kEncodeFiles); - if (state.fReporter->verbose()) SkDebugf("+"); + SkString filename(result.fFilename); + if (!filename.endsWith(".skp")) { + filename.append(".skp"); + } + TestResult::Test(result.fDirNo, filename.c_str(), kEncodeFiles); + if (reporter->verbose()) SkDebugf("+"); } +#endif } DEF_TEST(PathOpsSkpClip, reporter) { @@ -732,19 +928,177 @@ DEF_TEST(PathOpsSkpClipThreaded, reporter) { } int threadCount = reporter->allowThreaded() ? SkThreadPool::kThreadPerCore : 1; TestRunner testRunner(reporter, threadCount); - for (int dirNo = 1; dirNo <= 100; ++dirNo) { - *testRunner.fRunnables.append() = SkNEW_ARGS(TestRunnable, + const int firstDirNo = 1; + for (int dirNo = firstDirNo; dirNo <= 100; ++dirNo) { + *testRunner.fRunnables.append() = SkNEW_ARGS(TestRunnableDir, (&testSkpClipMain, dirNo, &testRunner)); } testRunner.render(); TestState state; state.init(0, reporter); - for (int dirNo = 1; dirNo <= 100; ++dirNo) { + for (int dirNo = firstDirNo; dirNo <= 100; ++dirNo) { TestState& testState = testRunner.fRunnables[dirNo - 1]->fState; + SkASSERT(testState.fResult.fDirNo == dirNo); for (int inner = 0; inner < testState.fPixelWorst.count(); ++inner) { - SkASSERT(testState.fResult.fDirNo == dirNo); addError(&state, testState.fPixelWorst[inner]); } + for (int inner = 0; inner < testState.fSlowest.count(); ++inner) { + addError(&state, testState.fSlowest[inner]); + } + } + encodeFound(reporter, state); +} + +static void testSkpClipUber(TestState* data) { + data->fResult.testOne(); + SkString dirName = make_stat_dir_name(data->fResult.fDirNo); + if (!dirName.size()) { + return; + } + SkString statName(data->fResult.fFilename); + SkASSERT(statName.endsWith(".skp")); + statName.remove(statName.size() - 4, 4); + statName.appendf(".%d.%d.skp", data->fResult.fPixelError, data->fResult.fTime); + SkString statusFile = make_filepath(data->fResult.fDirNo, outStatusDir, statName.c_str()); + SkFILE* file = sk_fopen(statusFile.c_str(), kWrite_SkFILE_Flag); + if (!file) { + SkDebugf("failed to create %s", statusFile.c_str()); + return; + } + sk_fclose(file); + if (data->fReporter->verbose()) { + if (data->fResult.fPixelError || data->fResult.fTime) { + SkDebugf("%s", data->fResult.progress().c_str()); + } else { + SkDebugf("."); + } + } +} + +static bool buildTests(skiatest::Reporter* reporter, int firstDirNo, int lastDirNo, SkTDArray<TestResult>* tests, + SkTDArray<SortByName*>* sorted) { + for (int dirNo = firstDirNo; dirNo <= lastDirNo; ++dirNo) { + SkString dirName = make_stat_dir_name(dirNo); + if (!dirName.size()) { + return false; + } + SkOSFile::Iter iter(dirName.c_str(), "skp"); + SkString filename; + while (iter.next(&filename)) { + TestResult test; + test.init(dirNo); + SkString spaceFile(filename); + char* spaces = spaceFile.writable_str(); + int spaceSize = (int) spaceFile.size(); + for (int index = 0; index < spaceSize; ++index) { + if (spaces[index] == '.') { + spaces[index] = ' '; + } + } + int success = sscanf(spaces, "%s %d %d skp", test.fFilename, + &test.fPixelError, &test.fTime); + if (success < 3) { + SkDebugf("failed to scan %s matched=%d\n", filename.c_str(), success); + return false; + } + *tests[dirNo - firstDirNo].append() = test; + } + if (!sorted) { + continue; + } + SkTDArray<TestResult>& testSet = tests[dirNo - firstDirNo]; + int count = testSet.count(); + for (int index = 0; index < count; ++index) { + *sorted[dirNo - firstDirNo].append() = (SortByName*) &testSet[index]; + } + if (sorted[dirNo - firstDirNo].count()) { + SkTQSort<SortByName>(sorted[dirNo - firstDirNo].begin(), + sorted[dirNo - firstDirNo].end() - 1); + if (reporter->verbose()) { + SkDebugf("+"); + } + } + } + return true; +} + +bool Less(const SortByName& a, const SortByName& b); +bool Less(const SortByName& a, const SortByName& b) { + return a < b; +} + +DEF_TEST(PathOpsSkpClipUberThreaded, reporter) { + const int firstDirNo = 1; + const int lastDirNo = 100; + if (!initUberTest(firstDirNo, lastDirNo)) { + return; + } + const int dirCount = lastDirNo - firstDirNo + 1; + SkTDArray<TestResult> tests[dirCount]; + SkTDArray<SortByName*> sorted[dirCount]; + if (!buildTests(reporter, firstDirNo, lastDirNo, tests, sorted)) { + return; + } + int threadCount = reporter->allowThreaded() ? SkThreadPool::kThreadPerCore : 1; + TestRunner testRunner(reporter, threadCount); + for (int dirNo = firstDirNo; dirNo <= lastDirNo; ++dirNo) { + SkString dirName = make_in_dir_name(dirNo); + if (!dirName.size()) { + continue; + } + SkOSFile::Iter iter(dirName.c_str(), "skp"); + SkString filename; + while (iter.next(&filename)) { + int count; + SortByName name; + for (size_t index = 0; index < skipOverSeptCount; ++index) { + if (skipOverSept[index].directory == dirNo + && strcmp(filename.c_str(), skipOverSept[index].filename) == 0) { + goto checkEarlyExit; + } + } + name.init(dirNo); + strncpy(name.fFilename, filename.c_str(), filename.size() - 4); // drop .skp + count = sorted[dirNo - firstDirNo].count(); + if (SkTSearch<SortByName, Less>(sorted[dirNo - firstDirNo].begin(), + count, &name, sizeof(&name)) < 0) { + *testRunner.fRunnables.append() = SkNEW_ARGS(TestRunnableFile, + (&testSkpClipUber, dirNo, filename.c_str(), &testRunner)); + } + checkEarlyExit: + ; + } + + } + testRunner.render(); + SkTDArray<TestResult> results[dirCount]; + if (!buildTests(reporter, firstDirNo, lastDirNo, results, NULL)) { + return; + } + SkTDArray<TestResult> allResults; + for (int dirNo = firstDirNo; dirNo <= lastDirNo; ++dirNo) { + SkTDArray<TestResult>& array = results[dirNo - firstDirNo]; + allResults.append(array.count(), array.begin()); + } + int allCount = allResults.count(); + SkTDArray<SortByPixel*> pixels; + SkTDArray<SortByTime*> times; + for (int index = 0; index < allCount; ++index) { + *pixels.append() = (SortByPixel*) &allResults[index]; + *times.append() = (SortByTime*) &allResults[index]; + } + TestState state; + if (pixels.count()) { + SkTQSort<SortByPixel>(pixels.begin(), pixels.end() - 1); + for (int inner = 0; inner < kMaxFiles; ++inner) { + *state.fPixelWorst.append() = *pixels[allCount - inner - 1]; + } + } + if (times.count()) { + SkTQSort<SortByTime>(times.begin(), times.end() - 1); + for (int inner = 0; inner < kMaxFiles; ++inner) { + *state.fSlowest.append() = *times[allCount - inner - 1]; + } } encodeFound(reporter, state); } diff --git a/tests/PathOpsSkpTest.cpp b/tests/PathOpsSkpTest.cpp index 5b10a1f736..3cfe4e2f7a 100755 --- a/tests/PathOpsSkpTest.cpp +++ b/tests/PathOpsSkpTest.cpp @@ -9,7 +9,6 @@ #define TEST(name) { name, #name } #define TRY_NEW_TESTS 0 -#define TRY_NEW_TESTS_IS_CLOSED 0 static void skpcheeseandburger_com225(skiatest::Reporter* reporter, const char* filename) { SkPath path; @@ -1809,15 +1808,6 @@ static void skpwww_heartiste_wordpress_com_86(skiatest::Reporter* reporter, cons testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); } -/* - * 125 SkASSERT(index < fCount); -(gdb) bt -#0 0x000000000041094b in SkTDArray<SkOpSpan>::operator[] (this=0x18, index=2) at ../../include/core/SkTDArray.h:125 -#1 0x00000000005ad2ce in SkOpSegment::tAtMid (this=0x0, start=2, end=5, mid=0.90000000000000002) at ../../src/pathops/SkOpSegment.h:219 -#2 0x00000000005aadea in contourRangeCheckY (contourList=..., currentPtr=0x7fffd77f4ec0, indexPtr=0x7fffd77f4f88, endIndexPtr=0x7fffd77f4f8c, bestHit=0x7fffd77f4ec8, - bestDx=0x7fffd77f4edc, tryAgain=0x7fffd77f4eff, midPtr=0x7fffd77f4e60, opp=false) at ../../src/pathops/SkPathOpsCommon.cpp:20 -#3 0x00000000005ab8ee in rightAngleWinding (contourList=..., current=0x7fffd77f4ec0, index=0x7fffd77f4f88, endIndex=0x7fffd77f4f8c, tHit=0x7fffd77f4ec8, hitDx=0x7fffd77f4edc, - */ #if TRY_NEW_TESTS static void skpwww_argus_presse_fr_41(skiatest::Reporter* reporter, const char* filename) { SkPath path; @@ -1988,8 +1978,6 @@ static void skpwww_hubbyscook_com_22(skiatest::Reporter* reporter, const char* f testPathOp(reporter, path, pathB, kDifference_PathOp, filename); } -// calcCoincidentWinding asserts in zeroSpan -#if TRY_NEW_TESTS static void skpwww_gruposejaumdivulgador_com_br_4(skiatest::Reporter* reporter, const char* filename) { SkPath path; path.setFillType(SkPath::kEvenOdd_FillType); @@ -2008,10 +1996,8 @@ static void skpwww_gruposejaumdivulgador_com_br_4(skiatest::Reporter* reporter, pathB.close(); testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); } -#endif // asserts in bridgeOp simple->isClosed() -#if TRY_NEW_TESTS_IS_CLOSED static void skpwww_phototransferapp_com_24(skiatest::Reporter* reporter, const char* filename) { SkPath path; path.setFillType(SkPath::kEvenOdd_FillType); @@ -2036,10 +2022,32 @@ static void skpwww_phototransferapp_com_24(skiatest::Reporter* reporter, const c pathB.close(); testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); } -#endif -// !simple->isClosed() -#if TRY_NEW_TESTS_IS_CLOSED +static void skpwww_phototransferapp_com_24x(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(85.6091843f, 5.92893219f); + path.quadTo(89.6041641f, 3, 93.7462997f, 3); + path.lineTo(112.74634f, 3); + path.quadTo(116.88843f, 3, 118.75134f, 5.92893219f); + path.quadTo(120.61414f, 8.85775471f, 119.10669f, 12.9996767f); + path.quadTo(120.46338f, 9.27196693f, 118.4939f, 6.63603878f); + path.quadTo(116.52441f, 4, 112.38232f, 4); + path.lineTo(93.3823318f, 4); + path.quadTo(89.2401962f, 4, 85.3518219f, 6.63603878f); + path.quadTo(81.4634476f, 9.27207756f, 80.1065979f, 13); + path.quadTo(81.614212f, 8.85786438f, 85.6091843f, 5.92893219f); + path.close(); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(83.7462997f, 3); + pathB.lineTo(122.74634f, 3); + pathB.lineTo(119.10657f, 13); + pathB.lineTo(80.1065979f, 13); + pathB.close(); + testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); +} + static void skpwww_helha_be_109(skiatest::Reporter* reporter, const char* filename) { SkPath path; path.setFillType(SkPath::kEvenOdd_FillType); @@ -2061,9 +2069,7 @@ static void skpwww_helha_be_109(skiatest::Reporter* reporter, const char* filena pathB.lineTo(104.291214f, 3359.87891f); testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); } -#endif -// !simple->isClosed() static void skpwww_cooksnaps_com_32(skiatest::Reporter* reporter, const char* filename) { SkPath path; path.setFillType(SkPath::kEvenOdd_FillType); @@ -2116,6 +2122,22 @@ static void skpwww_cooksnaps_com_32(skiatest::Reporter* reporter, const char* fi testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); } +static void skpwww_cooksnaps_com_32a(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(497.299988f, 176.896912f); + path.quadTo(493.678162f, 177.952286f, 490.183014f, 179.9702f); + path.lineTo(489.316986f, 180.4702f); + path.quadTo(485.175385f, 182.861359f, 482.115265f, 186.082397f); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(474.873322f, 199.293594f); + pathB.quadTo(478.196686f, 186.890503f, 489.316986f, 180.4702f); + pathB.lineTo(490.183014f, 179.9702f); + pathB.quadTo(501.303345f, 173.549896f, 513.706421f, 176.873276f); + testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); +} + // !simple->isClosed() static void skpwww_contextualnewsfeeds_com_346(skiatest::Reporter* reporter, const char* filename) { SkPath path; @@ -2182,8 +2204,6 @@ static void skpwww_karnivool_com_au_11(skiatest::Reporter* reporter, const char* testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); } -// computeonesumreverse calls markwinding with 0 winding -#if TRY_NEW_TESTS static void skpwww_tunero_de_24(skiatest::Reporter* reporter, const char* filename) { SkPath path; path.setFillType(SkPath::kEvenOdd_FillType); @@ -2222,10 +2242,7 @@ static void skpwww_tunero_de_24(skiatest::Reporter* reporter, const char* filena pathB.close(); testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); } -#endif -// rightAngleWinding (probably same as argus_presse) -#if TRY_NEW_TESTS static void skpwww_docgelo_com_66(skiatest::Reporter* reporter, const char* filename) { SkPath path; path.setFillType(SkPath::kEvenOdd_FillType); @@ -2243,9 +2260,7 @@ static void skpwww_docgelo_com_66(skiatest::Reporter* reporter, const char* file pathB.lineTo(185.5f, 24174.75f); testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); } -#endif -// joinCoincidence / findT / assert static void skpwww_kpopexplorer_net_22(skiatest::Reporter* reporter, const char* filename) { SkPath path; path.setFillType(SkPath::kEvenOdd_FillType); @@ -2274,8 +2289,6 @@ static void skpwww_kpopexplorer_net_22(skiatest::Reporter* reporter, const char* testPathOp(reporter, path, pathB, kDifference_PathOp, filename); } -// rightAngleWinding -#if TRY_NEW_TESTS static void skpwww_artblart_com_8(skiatest::Reporter* reporter, const char* filename) { SkPath path; path.setFillType(SkPath::kEvenOdd_FillType); @@ -2293,7 +2306,6 @@ static void skpwww_artblart_com_8(skiatest::Reporter* reporter, const char* file pathB.lineTo(45, 24527.5f); testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); } -#endif // joinCoincidence / findT / assert static void skpwww_jessicaslens_wordpress_com_222(skiatest::Reporter* reporter, const char* filename) { @@ -2746,6 +2758,38 @@ static void skpwww_wartepop_blogspot_com_br_6(skiatest::Reporter* reporter, cons testPathOp(reporter, path, pathB, kDifference_PathOp, filename); } +static void skpwww_wartepop_blogspot_com_br_6a(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(90.9763107f, 153.309662f); + path.quadTo(91.9526215f, 152.333344f, 93.3333359f, 152.333344f); + path.lineTo(124.666672f, 152.333344f); + path.quadTo(126.047379f, 152.333344f, 127.023689f, 153.309662f); + path.quadTo(128, 154.285965f, 128, 155.666672f); + path.lineTo(128, 163.666672f); + path.lineTo(90, 163.666672f); + path.lineTo(90, 155.666672f); + path.quadTo(90, 154.285965f, 90.9763107f, 153.309662f); + path.close(); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(90, 163.666672f); + pathB.lineTo(90, 155.666672f); + pathB.quadTo(90, 154.285965f, 90.9763107f, 153.309662f); + pathB.quadTo(91.9526215f, 152.333344f, 93.3333359f, 152.333344f); + pathB.lineTo(124.666672f, 152.333344f); + pathB.quadTo(125.909309f, 152.333344f, 126.787994f, 153.309662f); + pathB.quadTo(127.666672f, 154.285965f, 127.666672f, 155.666672f); + pathB.lineTo(127.666672f, 163.666672f); + pathB.lineTo(127.666672f, 163.666672f); + pathB.lineTo(127.666672f, 163.666672f); + pathB.lineTo(90, 163.666672f); + pathB.lineTo(90, 163.666672f); + pathB.lineTo(90, 163.666672f); + pathB.close(); + testPathOp(reporter, path, pathB, kDifference_PathOp, filename); +} + // !simple->isClosed() static void skpwww_odia_com_br_26(skiatest::Reporter* reporter, const char* filename) { SkPath path; @@ -2868,8 +2912,6 @@ static void skpwww_galaxystwo_com_4(skiatest::Reporter* reporter, const char* fi testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); } -// hangs in find top -#if TRY_NEW_TESTS static void skpwww_thaienews_blogspot_com_36(skiatest::Reporter* reporter, const char* filename) { SkPath path; path.setFillType(SkPath::kEvenOdd_FillType); @@ -2887,9 +2929,7 @@ static void skpwww_thaienews_blogspot_com_36(skiatest::Reporter* reporter, const pathB.lineTo(430.5f, 6268); testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); } -#endif -// hangs static void skpwww_fashionscandal_com_94(skiatest::Reporter* reporter, const char* filename) { SkPath path; path.setFillType(SkPath::kEvenOdd_FillType); @@ -2962,8 +3002,6 @@ static void skpwww_defense_studies_blogspot_com_64(skiatest::Reporter* reporter, testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); } -// checkSmall / addTPair / addT assert -#if TRY_NEW_TESTS static void skpwww_uniquefx_net_442(skiatest::Reporter* reporter, const char* filename) { SkPath path; path.setFillType(SkPath::kEvenOdd_FillType); @@ -2981,10 +3019,7 @@ static void skpwww_uniquefx_net_442(skiatest::Reporter* reporter, const char* fi pathB.lineTo(1019, 305); testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); } -#endif -// rightAngleWinding -#if TRY_NEW_TESTS static void skpwww_kitcheninspirations_wordpress_com_32(skiatest::Reporter* reporter, const char* filename) { SkPath path; path.setFillType(SkPath::kEvenOdd_FillType); @@ -3002,58 +3037,570 @@ static void skpwww_kitcheninspirations_wordpress_com_32(skiatest::Reporter* repo pathB.lineTo(65.8333359f, 19651.5f); testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); } -#endif + +static void skpwww_educationalcraft_com_4(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(941, 1494); + path.lineTo(941, 1464); + path.lineTo(985, 1464); + path.lineTo(985, 1494); + path.lineTo(941, 1494); + path.close(); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(979.211975f, 1480.45496f); + pathB.cubicTo(979.211975f, 1480.45496f, 976.348999f, 1479.68506f, 977.495972f, 1475.59497f); + pathB.cubicTo(977.497009f, 1475.59497f, 981.072021f, 1477.88501f, 979.211975f, 1480.45496f); + pathB.close(); + pathB.moveTo(977.854004f, 1484.453f); + pathB.cubicTo(977.854004f, 1484.453f, 975.265991f, 1483.26099f, 976.713989f, 1479.35205f); + pathB.cubicTo(976.713989f, 1479.35303f, 979.84198f, 1482.23499f, 977.854004f, 1484.453f); + pathB.close(); + pathB.moveTo(980.226013f, 1476.229f); + pathB.cubicTo(980.226013f, 1476.229f, 977.078003f, 1476.349f, 977.234985f, 1471.97095f); + pathB.cubicTo(977.234985f, 1471.97095f, 980.666992f, 1473.12903f, 980.226013f, 1476.229f); + pathB.close(); + pathB.moveTo(984.546021f, 1478.31494f); + pathB.cubicTo(984.546021f, 1478.31494f, 983.187988f, 1481.93396f, 980.026001f, 1481.276f); + pathB.cubicTo(980.026978f, 1481.276f, 979.554993f, 1478.38904f, 984.546021f, 1478.31494f); + pathB.close(); + pathB.moveTo(978.989014f, 1484.198f); + pathB.cubicTo(978.989014f, 1484.198f, 979.094971f, 1481.33496f, 983.786011f, 1481.823f); + pathB.cubicTo(983.786011f, 1481.823f, 982.070007f, 1485.49805f, 978.989014f, 1484.198f); + pathB.close(); + pathB.moveTo(976.393005f, 1486.86804f); + pathB.cubicTo(976.393005f, 1486.86804f, 976.719971f, 1484.06494f, 981.679016f, 1485.37f); + pathB.cubicTo(981.679016f, 1485.37f, 979.169983f, 1488.40796f, 976.393005f, 1486.86804f); + pathB.close(); + pathB.moveTo(969.156982f, 1490.40002f); + pathB.cubicTo(969.156982f, 1490.40002f, 971.478027f, 1488.23596f, 974.869995f, 1491.21399f); + pathB.cubicTo(974.869995f, 1491.21497f, 970.828003f, 1493.026f, 969.156982f, 1490.40002f); + pathB.close(); + pathB.moveTo(972.825012f, 1483.93701f); + pathB.cubicTo(972.825012f, 1483.93701f, 973.971985f, 1487.98401f, 971.161987f, 1488.94604f); + pathB.cubicTo(971.161987f, 1488.94495f, 969.278015f, 1486.37097f, 972.825012f, 1483.93701f); + pathB.close(); + pathB.moveTo(965.60199f, 1489.98499f); + pathB.cubicTo(965.60199f, 1489.98499f, 964.879028f, 1487.19202f, 969.864014f, 1486.75f); + pathB.cubicTo(969.864014f, 1486.75f, 968.749023f, 1490.672f, 965.60199f, 1489.98499f); + pathB.close(); + pathB.moveTo(970.666992f, 1492.81604f); + pathB.cubicTo(970.666992f, 1492.81604f, 967.327026f, 1494.49695f, 964.999023f, 1491.56299f); + pathB.cubicTo(964.999023f, 1491.56299f, 967.304016f, 1489.43896f, 970.666992f, 1492.81604f); + pathB.close(); + pathB.moveTo(968.343994f, 1481.53796f); + pathB.cubicTo(971.573975f, 1479.94995f, 971.687988f, 1476.78601f, 971.687988f, 1476.78601f); + pathB.lineTo(971.393982f, 1466.83398f); + pathB.lineTo(954.960999f, 1466.83398f); + pathB.lineTo(954.666016f, 1476.78601f); + pathB.cubicTo(954.666016f, 1476.78601f, 954.780029f, 1479.94995f, 958.008972f, 1481.53796f); + pathB.cubicTo(960.781006f, 1482.90295f, 962.166992f, 1484.77698f, 962.166992f, 1484.77698f); + pathB.cubicTo(962.166992f, 1484.77698f, 962.747986f, 1485.70105f, 963.177979f, 1485.70105f); + pathB.cubicTo(963.606995f, 1485.70105f, 964.185974f, 1484.77698f, 964.185974f, 1484.77698f); + pathB.cubicTo(964.185974f, 1484.77698f, 965.573975f, 1482.90295f, 968.343994f, 1481.53796f); + pathB.close(); + pathB.moveTo(963.215027f, 1486.67004f); + pathB.cubicTo(962.744995f, 1486.67004f, 962.106995f, 1485.65405f, 962.106995f, 1485.65405f); + pathB.cubicTo(962.106995f, 1485.65405f, 960.585022f, 1483.59595f, 957.539001f, 1482.09705f); + pathB.cubicTo(953.991028f, 1480.35205f, 953.867004f, 1476.87598f, 953.867004f, 1476.87598f); + pathB.lineTo(954.190002f, 1465.94397f); + pathB.lineTo(972.23999f, 1465.94397f); + pathB.lineTo(972.565002f, 1476.87695f); + pathB.cubicTo(972.565002f, 1476.87695f, 972.440979f, 1480.35303f, 968.891968f, 1482.09802f); + pathB.cubicTo(965.846008f, 1483.59705f, 964.325012f, 1485.65503f, 964.325012f, 1485.65503f); + pathB.cubicTo(964.325012f, 1485.65503f, 963.687012f, 1486.67004f, 963.215027f, 1486.67004f); + pathB.close(); + pathB.moveTo(960.68103f, 1489.98499f); + pathB.cubicTo(957.533997f, 1490.672f, 956.417969f, 1486.75f, 956.417969f, 1486.75f); + pathB.cubicTo(961.403015f, 1487.19202f, 960.68103f, 1489.98499f, 960.68103f, 1489.98499f); + pathB.close(); + pathB.moveTo(963.143005f, 1489.59802f); + pathB.cubicTo(963.763f, 1489.59802f, 964.265015f, 1490.09998f, 964.265015f, 1490.72095f); + pathB.cubicTo(964.265015f, 1491.34204f, 963.763f, 1491.84399f, 963.143005f, 1491.84399f); + pathB.cubicTo(962.521973f, 1491.84399f, 962.02002f, 1491.34204f, 962.02002f, 1490.72095f); + pathB.cubicTo(962.02002f, 1490.09998f, 962.521973f, 1489.59802f, 963.143005f, 1489.59802f); + pathB.close(); + pathB.moveTo(961.283997f, 1491.56299f); + pathB.cubicTo(958.953979f, 1494.49695f, 955.61499f, 1492.81604f, 955.61499f, 1492.81604f); + pathB.cubicTo(958.97699f, 1489.43896f, 961.283997f, 1491.56299f, 961.283997f, 1491.56299f); + pathB.close(); + pathB.moveTo(957.127014f, 1490.40002f); + pathB.cubicTo(955.455017f, 1493.026f, 951.414001f, 1491.21399f, 951.414001f, 1491.21399f); + pathB.cubicTo(954.802979f, 1488.23596f, 957.127014f, 1490.40002f, 957.127014f, 1490.40002f); + pathB.close(); + pathB.moveTo(949.890991f, 1486.86804f); + pathB.cubicTo(947.112976f, 1488.40796f, 944.604004f, 1485.37f, 944.604004f, 1485.37f); + pathB.cubicTo(949.562012f, 1484.06494f, 949.890991f, 1486.86804f, 949.890991f, 1486.86804f); + pathB.close(); + pathB.moveTo(947.070984f, 1480.45496f); + pathB.cubicTo(945.211975f, 1477.88501f, 948.786011f, 1475.59497f, 948.786011f, 1475.59497f); + pathB.cubicTo(949.934021f, 1479.68506f, 947.070984f, 1480.45496f, 947.070984f, 1480.45496f); + pathB.close(); + pathB.moveTo(946.054016f, 1476.229f); + pathB.cubicTo(945.61499f, 1473.12903f, 949.046997f, 1471.97095f, 949.046997f, 1471.97095f); + pathB.cubicTo(949.205994f, 1476.349f, 946.054016f, 1476.229f, 946.054016f, 1476.229f); + pathB.close(); + pathB.moveTo(948.427002f, 1484.453f); + pathB.cubicTo(946.440002f, 1482.23499f, 949.567993f, 1479.35205f, 949.567993f, 1479.35205f); + pathB.cubicTo(951.015991f, 1483.26099f, 948.427002f, 1484.453f, 948.427002f, 1484.453f); + pathB.close(); + pathB.moveTo(947.294006f, 1484.198f); + pathB.cubicTo(944.210999f, 1485.49805f, 942.495972f, 1481.823f, 942.495972f, 1481.823f); + pathB.cubicTo(947.187988f, 1481.33496f, 947.294006f, 1484.198f, 947.294006f, 1484.198f); + pathB.close(); + pathB.moveTo(946.255005f, 1481.276f); + pathB.cubicTo(943.094971f, 1481.93396f, 941.736023f, 1478.31494f, 941.736023f, 1478.31494f); + pathB.cubicTo(946.728027f, 1478.38904f, 946.255005f, 1481.276f, 946.255005f, 1481.276f); + pathB.close(); + pathB.moveTo(945.312988f, 1478.18005f); + pathB.cubicTo(942.052979f, 1477.80103f, 942.651001f, 1473.87805f, 942.651001f, 1473.87805f); + pathB.cubicTo(946.562988f, 1475.66199f, 945.312988f, 1478.18005f, 945.312988f, 1478.18005f); + pathB.close(); + pathB.moveTo(945.382019f, 1474.328f); + pathB.cubicTo(942.924011f, 1472.729f, 944.492004f, 1469.48706f, 944.492004f, 1469.48706f); + pathB.cubicTo(947.388977f, 1471.95703f, 945.382019f, 1474.328f, 945.382019f, 1474.328f); + pathB.close(); + pathB.moveTo(946.797974f, 1470.27405f); + pathB.cubicTo(944.664978f, 1467.90198f, 947.083984f, 1465.50598f, 947.083984f, 1465.50598f); + pathB.cubicTo(949.145996f, 1468.82605f, 946.797974f, 1470.27405f, 946.797974f, 1470.27405f); + pathB.close(); + pathB.moveTo(947.392029f, 1471.64197f); + pathB.cubicTo(947.624023f, 1468.56299f, 951.361023f, 1468.29199f, 951.361023f, 1468.29199f); + pathB.cubicTo(950.554016f, 1471.98499f, 947.392029f, 1471.64197f, 947.392029f, 1471.64197f); + pathB.close(); + pathB.moveTo(948.64801f, 1468.15002f); + pathB.cubicTo(948.638977f, 1465.22095f, 952.265991f, 1464.46399f, 952.265991f, 1464.46399f); + pathB.cubicTo(951.672974f, 1468.53101f, 948.64801f, 1468.15002f, 948.64801f, 1468.15002f); + pathB.close(); + pathB.moveTo(951.176025f, 1486.97803f); + pathB.cubicTo(948.963013f, 1484.62f, 951.361023f, 1481.77698f, 951.361023f, 1481.77698f); + pathB.cubicTo(953.734985f, 1485.48596f, 951.176025f, 1486.97803f, 951.176025f, 1486.97803f); + pathB.close(); + pathB.moveTo(947.51001f, 1488.53101f); + pathB.cubicTo(947.51001f, 1488.53101f, 951.596985f, 1486.32202f, 953.234009f, 1489.08997f); + pathB.cubicTo(953.234009f, 1489.08997f, 951.158997f, 1491.03601f, 947.51001f, 1488.53101f); + pathB.close(); + pathB.moveTo(955.120972f, 1488.94495f); + pathB.cubicTo(952.309021f, 1487.98303f, 953.458984f, 1483.93604f, 953.458984f, 1483.93604f); + pathB.cubicTo(957.004028f, 1486.37097f, 955.120972f, 1488.94495f, 955.120972f, 1488.94495f); + pathB.close(); + pathB.moveTo(978.770996f, 1488.53101f); + pathB.cubicTo(975.122986f, 1491.03601f, 973.047974f, 1489.08997f, 973.047974f, 1489.08997f); + pathB.cubicTo(974.684998f, 1486.32202f, 978.770996f, 1488.53101f, 978.770996f, 1488.53101f); + pathB.close(); + pathB.moveTo(975.106995f, 1486.97803f); + pathB.cubicTo(975.106995f, 1486.97803f, 972.546997f, 1485.48706f, 974.919983f, 1481.77698f); + pathB.cubicTo(974.919983f, 1481.776f, 977.31897f, 1484.61902f, 975.106995f, 1486.97803f); + pathB.close(); + pathB.moveTo(974.016968f, 1464.46399f); + pathB.cubicTo(974.016968f, 1464.46399f, 977.643982f, 1465.22095f, 977.633972f, 1468.15002f); + pathB.cubicTo(977.633972f, 1468.15002f, 974.611023f, 1468.53101f, 974.016968f, 1464.46399f); + pathB.close(); + pathB.moveTo(974.919983f, 1468.29199f); + pathB.cubicTo(974.919983f, 1468.29199f, 978.658997f, 1468.56299f, 978.890015f, 1471.64197f); + pathB.cubicTo(978.890015f, 1471.64197f, 975.72699f, 1471.98499f, 974.919983f, 1468.29199f); + pathB.close(); + pathB.moveTo(979.197998f, 1465.50598f); + pathB.cubicTo(979.197998f, 1465.50598f, 981.619019f, 1467.90198f, 979.481995f, 1470.27405f); + pathB.cubicTo(979.481995f, 1470.27405f, 977.138f, 1468.82605f, 979.197998f, 1465.50598f); + pathB.close(); + pathB.moveTo(980.900024f, 1474.328f); + pathB.cubicTo(980.900024f, 1474.328f, 978.893005f, 1471.95703f, 981.791016f, 1469.48706f); + pathB.cubicTo(981.791016f, 1469.48596f, 983.358032f, 1472.729f, 980.900024f, 1474.328f); + pathB.close(); + pathB.moveTo(980.968994f, 1478.18005f); + pathB.cubicTo(980.968994f, 1478.18005f, 979.718018f, 1475.66199f, 983.632019f, 1473.87805f); + pathB.cubicTo(983.632019f, 1473.87805f, 984.229004f, 1477.80103f, 980.968994f, 1478.18005f); + pathB.close(); + testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); +} + +static void skpwww_narayana_publishers_com_194(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(1083.34314f, 445.65686f); + path.quadTo(1081, 443.313721f, 1081, 440); + path.lineTo(1257, 440); + path.quadTo(1257, 443.313721f, 1254.65686f, 445.65686f); + path.quadTo(1252.31372f, 448, 1249, 448); + path.lineTo(1089, 448); + path.quadTo(1085.68628f, 448, 1083.34314f, 445.65686f); + path.close(); + path.moveTo(1083, 441); + path.lineTo(1255, 441); + path.quadTo(1255, 443.071075f, 1253.53552f, 444.535522f); + path.quadTo(1252.07104f, 446, 1250, 446); + path.lineTo(1088, 446); + path.quadTo(1085.92896f, 446, 1084.46448f, 444.535522f); + path.quadTo(1083, 443.071075f, 1083, 441); + path.close(); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(1081, 440); + pathB.lineTo(1082, 440); + pathB.lineTo(1090.01001f, 448); + pathB.lineTo(1081, 448); + testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); +} + +static void skpwww_cooksnaps_com_17(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(170.340179f, 176); + path.lineTo(166, 176); + path.quadTo(161.964188f, 176, 158.299957f, 176.896912f); + path.quadTo(154.678162f, 177.952271f, 151.183014f, 179.9702f); + path.lineTo(150.316986f, 180.4702f); + path.quadTo(146.175812f, 182.861099f, 143.115921f, 186.081696f); + path.quadTo(140.693939f, 188.70134f, 138.99472f, 191.620407f); + path.quadTo(137.316833f, 194.550888f, 136.259338f, 197.957367f); + path.quadTo(135, 202.217865f, 135, 207); + path.lineTo(135, 208); + path.quadTo(135, 212.035751f, 135.896912f, 215.699997f); + path.quadTo(136.952286f, 219.321869f, 138.9702f, 222.816986f); + path.lineTo(139.4702f, 223.683014f); + path.quadTo(141.861099f, 227.824188f, 145.081696f, 230.884079f); + path.quadTo(147.70134f, 233.306061f, 150.620407f, 235.00528f); + path.quadTo(153.550888f, 236.683167f, 156.957367f, 237.740662f); + path.quadTo(161.217865f, 239, 166, 239); + path.lineTo(170.482162f, 239); + path.quadTo(176.307037f, 238.210968f, 181.816986f, 235.0298f); + path.lineTo(182.683014f, 234.5298f); + path.quadTo(182.686462f, 234.527817f, 182.689896f, 234.525818f); + path.quadTo(193.804352f, 228.105652f, 197.126709f, 215.70639f); + path.quadTo(200.450104f, 203.303314f, 194.0298f, 192.183014f); + path.lineTo(193.5298f, 191.316986f); + path.quadTo(187.109497f, 180.196686f, 174.706406f, 176.873276f); + path.quadTo(172.503067f, 176.282898f, 170.340179f, 176); + path.close(); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(139.4702f, 223.683014f); + pathB.lineTo(138.9702f, 222.816986f); + pathB.quadTo(132.549896f, 211.696686f, 135.873291f, 199.293594f); + pathB.quadTo(139.196686f, 186.890503f, 150.316986f, 180.4702f); + pathB.lineTo(151.183014f, 179.9702f); + pathB.quadTo(162.303314f, 173.549896f, 174.706406f, 176.873276f); + pathB.quadTo(187.109497f, 180.196686f, 193.5298f, 191.316986f); + pathB.lineTo(194.0298f, 192.183014f); + pathB.quadTo(200.450104f, 203.303314f, 197.126709f, 215.70639f); + pathB.quadTo(193.803314f, 228.109497f, 182.683014f, 234.5298f); + pathB.lineTo(181.816986f, 235.0298f); + pathB.quadTo(170.696686f, 241.450104f, 158.293594f, 238.126709f); + pathB.quadTo(145.890503f, 234.803314f, 139.4702f, 223.683014f); + pathB.close(); + testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); +} + +static void skpwww_swapspacesystems_com_5(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(819.050781f, 5539.72412f); + path.quadTo(819.651672f, 5539.1543f, 820.479858f, 5539.17578f); + path.lineTo(1191.35278f, 5548.8877f); + path.quadTo(1192.18091f, 5548.90918f, 1192.7511f, 5549.50977f); + path.quadTo(1193.32141f, 5550.11133f, 1193.29968f, 5550.93945f); + path.lineTo(1186.57214f, 5807.85107f); + path.quadTo(1186.55054f, 5808.6792f, 1185.94958f, 5809.24951f); + path.quadTo(1185.34863f, 5809.81982f, 1184.52051f, 5809.79834f); + path.lineTo(813.647705f, 5800.08643f); + path.quadTo(812.819519f, 5800.06494f, 812.249268f, 5799.46387f); + path.quadTo(811.679016f, 5798.86279f, 811.700684f, 5798.03467f); + path.lineTo(818.428162f, 5541.12305f); + path.quadTo(818.44989f, 5540.29492f, 819.050781f, 5539.72412f); + path.close(); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(818.48053f, 5539.12354f); + pathB.lineTo(1193.35205f, 5548.93994f); + pathB.lineTo(1186.5199f, 5809.85059f); + pathB.lineTo(811.648376f, 5800.03418f); + pathB.close(); + testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); +} + +static void skpwww_kitcheninspirations_wordpress_com_66(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(47.1666679f, 27820.668f); + path.lineTo(60.8333359f, 27820.668f); + path.lineTo(60.8333359f, 27820.498f); + path.lineTo(47.1666679f, 27820.5f); + path.lineTo(47.1666679f, 27820.668f); + path.close(); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(47.1666679f, 27820.668f); + pathB.lineTo(47.1666679f, 27820.498f); + pathB.lineTo(60.8333359f, 27820.5f); + pathB.lineTo(60.8333359f, 27820.668f); + testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); +} + +static void skpwww_etiqadd_com_2464(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(630.378662f, 1293.42896f); + path.quadTo(631.257385f, 1292.55029f, 632.5f, 1292.55029f); + path.quadTo(633.742615f, 1292.55029f, 634.621338f, 1293.42896f); + path.lineTo(639.571045f, 1298.37866f); + path.quadTo(640.449768f, 1299.25732f, 640.449707f, 1300.5f); + path.quadTo(640.449768f, 1301.74268f, 639.571045f, 1302.62134f); + path.lineTo(634.621338f, 1307.57104f); + path.quadTo(633.742615f, 1308.44971f, 632.5f, 1308.44971f); + path.quadTo(631.257385f, 1308.44971f, 630.378662f, 1307.57104f); + path.lineTo(625.428955f, 1302.62134f); + path.quadTo(624.550232f, 1301.74268f, 624.550293f, 1300.5f); + path.quadTo(624.550232f, 1299.25732f, 625.428955f, 1298.37866f); + path.lineTo(630.378662f, 1293.42896f); + path.close(); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(632.5f, 1291.30762f); + pathB.lineTo(641.692383f, 1300.5f); + pathB.lineTo(632.5f, 1309.69238f); + pathB.lineTo(623.307617f, 1300.5f); + pathB.close(); + testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); +} + +static void skpwww_narayana_verlag_de_194(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(1083.34314f, 513.65686f); + path.quadTo(1081, 511.313721f, 1081, 508); + path.lineTo(1257, 508); + path.quadTo(1257, 511.313721f, 1254.65686f, 513.65686f); + path.quadTo(1252.31372f, 516, 1249, 516); + path.lineTo(1089, 516); + path.quadTo(1085.68628f, 516, 1083.34314f, 513.65686f); + path.close(); + path.moveTo(1083, 509); + path.lineTo(1255, 509); + path.quadTo(1255, 511.071075f, 1253.53552f, 512.535522f); + path.quadTo(1252.07104f, 514, 1250, 514); + path.lineTo(1088, 514); + path.quadTo(1085.92896f, 514, 1084.46448f, 512.535522f); + path.quadTo(1083, 511.071075f, 1083, 509); + path.close(); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(1081, 508); + pathB.lineTo(1082, 508); + pathB.lineTo(1090.01001f, 516); + pathB.lineTo(1081, 516); + testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); +} + +static void skpwww_americascup_com_108(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(999.454102f, 689.17157f); + path.quadTo(1001.172f, 688, 1002.82886f, 688); + path.lineTo(1013.82886f, 688); + path.lineTo(1002.17114f, 713); + path.lineTo(991.171143f, 713); + path.quadTo(989.514282f, 713, 988.889038f, 711.82843f); + path.quadTo(988.263794f, 710.65686f, 989.036377f, 709); + path.lineTo(996.963623f, 692); + path.quadTo(997.736206f, 690.34314f, 999.454102f, 689.17157f); + path.close(); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(998.828857f, 688); + pathB.lineTo(1013.82886f, 688); + pathB.lineTo(1002.17114f, 713); + pathB.lineTo(987.171143f, 713); + pathB.close(); + testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); +} + +static void skpwww_vantageproduction_com_109(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(794.514709f, 759.485291f); + path.quadTo(791, 755.970581f, 791, 751); + path.lineTo(1133, 751); + path.quadTo(1133, 755.970581f, 1129.48523f, 759.485291f); + path.quadTo(1125.97058f, 763, 1121, 763); + path.lineTo(803, 763); + path.quadTo(798.029419f, 763, 794.514709f, 759.485291f); + path.close(); + path.moveTo(793, 752); + path.lineTo(1131, 752); + path.quadTo(1131, 755.727905f, 1128.36401f, 758.363953f); + path.quadTo(1125.72791f, 761, 1122, 761); + path.lineTo(802, 761); + path.quadTo(798.272095f, 761, 795.636047f, 758.363953f); + path.quadTo(793, 755.727905f, 793, 752); + path.close(); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(791, 751); + pathB.lineTo(792, 751); + pathB.lineTo(804.01001f, 763); + pathB.lineTo(791, 763); + testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); +} + +static void skpwww_aceinfographics_com_106(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(166.878677f, 7638.87891f); + path.quadTo(166, 7639.75732f, 166, 7641); + path.lineTo(166, 11577); + path.quadTo(166, 11578.2422f, 166.878677f, 11579.1211f); + path.quadTo(167.388f, 11579.6309f, 168.019989f, 11579.8447f); + path.lineTo(168.019974f, 11576.2979f); + path.quadTo(168, 11576.1533f, 168, 11576); + path.lineTo(168, 7642); + path.lineTo(168.000015f, 7641.99316f); + path.lineTo(168, 7640); + path.lineTo(166.878677f, 7638.87891f); + path.close(); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(166, 7638); + pathB.lineTo(168.020004f, 7635.97998f); + pathB.lineTo(168, 11578); + pathB.lineTo(166, 11580); + testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); +} + +static void skpwww_tcmevents_org_13(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(465.951904f, 547.960144f); + path.quadTo(465.66571f, 546.867371f, 465.404938f, 546); + path.lineTo(465.504089f, 546); + path.quadTo(465.670349f, 546.601257f, 465.84668f, 547.288391f); + path.quadTo(467.274506f, 552.852356f, 468.506836f, 560.718567f); + path.quadTo(467.336121f, 553.24585f, 465.951904f, 547.960144f); + path.close(); + path.moveTo(470.591064f, 574.024353f); + path.quadTo(474.844055f, 601.176025f, 471.728271f, 620.364502f); + path.quadTo(470.567017f, 627.515991f, 468.635742f, 632); + path.lineTo(469.106812f, 632); + path.quadTo(470.791504f, 627.638672f, 471.833496f, 621.036255f); + path.quadTo(474.905701f, 601.569519f, 470.591064f, 574.024353f); + path.close(); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(322.992462f, 541.475708f); + pathB.lineTo(465.531616f, 541.724426f); + pathB.lineTo(468.507751f, 560.724426f); + pathB.lineTo(325.968597f, 560.475708f); + pathB.close(); + testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); +} + +static void skpwww_paseoitaigara_com_br_56(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(633.147217f, 1247); + path.lineTo(718, 1162.14722f); + path.lineTo(802.852783f, 1247); + path.lineTo(718, 1331.85278f); + path.lineTo(633.147217f, 1247); + path.close(); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(635.268494f, 1244.87866f); + pathB.lineTo(715.878662f, 1164.26855f); + pathB.quadTo(716.757385f, 1163.38989f, 718, 1163.38989f); + pathB.quadTo(719.242615f, 1163.38989f, 720.121338f, 1164.26855f); + pathB.lineTo(800.731506f, 1244.87866f); + pathB.quadTo(801.610168f, 1245.75732f, 801.610168f, 1247); + pathB.quadTo(801.610229f, 1248.24268f, 800.731445f, 1249.12134f); + pathB.lineTo(720.121338f, 1329.73145f); + pathB.quadTo(719.242676f, 1330.61011f, 718, 1330.61011f); + pathB.quadTo(716.757385f, 1330.61011f, 715.878723f, 1329.73145f); + pathB.lineTo(635.268555f, 1249.12134f); + pathB.quadTo(634.389832f, 1248.24268f, 634.389832f, 1247); + pathB.quadTo(634.389832f, 1245.75732f, 635.268494f, 1244.87866f); + pathB.close(); + testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); +} + +static void skpwww_mortgagemarketguide_com_109(skiatest::Reporter* reporter, const char* filename) { + SkPath path; + path.setFillType(SkPath::kEvenOdd_FillType); + path.moveTo(816.514709f, 781.485291f); + path.quadTo(813, 777.970581f, 813, 773); + path.lineTo(1133, 773); + path.quadTo(1133, 777.970581f, 1129.48523f, 781.485291f); + path.quadTo(1125.97058f, 785, 1121, 785); + path.lineTo(825, 785); + path.quadTo(820.029419f, 785, 816.514709f, 781.485291f); + path.close(); + path.moveTo(815, 774); + path.lineTo(1131, 774); + path.quadTo(1131, 777.727905f, 1128.36401f, 780.363953f); + path.quadTo(1125.72791f, 783, 1122, 783); + path.lineTo(824, 783); + path.quadTo(820.272095f, 783, 817.636047f, 780.363953f); + path.quadTo(815, 777.727905f, 815, 774); + path.close(); + SkPath pathB; + pathB.setFillType(SkPath::kWinding_FillType); + pathB.moveTo(813, 773); + pathB.lineTo(814, 773); + pathB.lineTo(826.01001f, 785); + pathB.lineTo(813, 785); + testPathOp(reporter, path, pathB, kIntersect_PathOp, filename); +} static void (*firstTest)(skiatest::Reporter* , const char* filename) = 0; static struct TestDesc tests[] = { + TEST(skpwww_wartepop_blogspot_com_br_6), + TEST(skpwww_wartepop_blogspot_com_br_6a), + TEST(skpwww_cooksnaps_com_32a), #if TRY_NEW_TESTS - TEST(skpwww_kitcheninspirations_wordpress_com_32), // rightanglewinding -#endif -#if TRY_NEW_TESTS - TEST(skpwww_uniquefx_net_442), // checkSmall / addTPair / addT assert + TEST(skpwww_argus_presse_fr_41), #endif + TEST(skpwww_cooksnaps_com_17), + TEST(skpwww_cooksnaps_com_32), + TEST(skpwww_kitcheninspirations_wordpress_com_66), + TEST(skpwww_tcmevents_org_13), + TEST(skpwww_narayana_publishers_com_194), + TEST(skpwww_swapspacesystems_com_5), + TEST(skpwww_vantageproduction_com_109), + TEST(skpwww_americascup_com_108), + TEST(skpwww_narayana_verlag_de_194), + TEST(skpwww_etiqadd_com_2464), + TEST(skpwww_paseoitaigara_com_br_56), + TEST(skpwww_mortgagemarketguide_com_109), + TEST(skpwww_aceinfographics_com_106), + TEST(skpwww_educationalcraft_com_4), + TEST(skpwww_kitcheninspirations_wordpress_com_32), + TEST(skpwww_artblart_com_8), + TEST(skpwww_docgelo_com_66), + TEST(skpwww_uniquefx_net_442), TEST(skpwww_defense_studies_blogspot_com_64), TEST(skpwww_kenlevine_blogspot_com_28), TEST(skpwww_fashionscandal_com_94), -#if TRY_NEW_TESTS - TEST(skpwww_thaienews_blogspot_com_36), // completes but fails to produce correct output -#endif + TEST(skpwww_thaienews_blogspot_com_36), TEST(skpwww_galaxystwo_com_4), TEST(skpwww_catingueiraonline_com_352), TEST(skpwww_evolvehq_com_210), - TEST(skpwww_odia_com_br_26), // asserts expecting isClosed - TEST(skpwww_wartepop_blogspot_com_br_6), // asserts expecting isClosed + TEST(skpwww_odia_com_br_26), TEST(skpwww_lokado_de_173), TEST(skpwww_seopack_blogspot_com_2153), TEST(skpwww_partsdata_de_53), TEST(skpwww_simplysaru_com_40), TEST(skpwww_jessicaslens_wordpress_com_222), -#if TRY_NEW_TESTS - TEST(skpwww_artblart_com_8), // rightanglewinding -#endif TEST(skpwww_kpopexplorer_net_22), -#if TRY_NEW_TESTS - TEST(skpwww_docgelo_com_66), // rightanglewinding -#endif -#if TRY_NEW_TESTS // nearly coincident curves -- maybe angle is written before coincidence detected? - TEST(skpwww_tunero_de_24), // has both winding and oppWinding set to zero in markWinding -#endif + TEST(skpwww_tunero_de_24), TEST(skpwww_karnivool_com_au_11), TEST(skpwww_pindosiya_com_99), - TEST(skpwww_contextualnewsfeeds_com_346), // asserts expecting isClosed - TEST(skpwww_cooksnaps_com_32), // asserts expecting isClosed -#if TRY_NEW_TESTS_IS_CLOSED - TEST(skpwww_helha_be_109), // asserts expecting isClosed - TEST(skpwww_phototransferapp_com_24), // asserts expecting isClosed -#endif -#if TRY_NEW_TESTS - TEST(skpwww_gruposejaumdivulgador_com_br_4), // span already marked done is futher marked coin -#endif + TEST(skpwww_contextualnewsfeeds_com_346), + TEST(skpwww_helha_be_109), + TEST(skpwww_phototransferapp_com_24), + TEST(skpwww_phototransferapp_com_24x), + TEST(skpwww_gruposejaumdivulgador_com_br_4), TEST(skpwww_hubbyscook_com_22), -#if TRY_NEW_TESTS - TEST(skpwww_argus_presse_fr_41), // rightanglewinding -#endif TEST(skpwww_maturesupertube_com_21), TEST(skpwww_getgold_jp_731), TEST(skpwww_trashness_com_36), @@ -3072,17 +3619,17 @@ static struct TestDesc tests[] = { TEST(skpskpicture15), TEST(skpwww_meb_gov_tr_6), TEST(skpwww_sciality_com_101), - TEST(skpwww_booking_com_68), // similar to lavoixdunord - TEST(skpwww_despegar_com_mx_272), // similar to lavoixdunord - TEST(skpwww_lavoixdunord_fr_11), // not quite coincident, sorting line/cubic fails - TEST(skppptv_com_62), // cubic have nearly identical tangents, sort incorrectly + TEST(skpwww_booking_com_68), + TEST(skpwww_despegar_com_mx_272), + TEST(skpwww_lavoixdunord_fr_11), + TEST(skppptv_com_62), TEST(skppchappy_com_au102), TEST(skpsciality_com161), TEST(skpi_gino_com16), - TEST(skpnaoxrane_ru23), // see test for failure evaluation - TEST(skptcmevents_org23), // see test for (partial) failure evaluation - TEST(skpredbullskatearcade_es16), // cubic have nearly identical tangents, sort incorrectly - TEST(skpfinanzasdigital_com9), // cubic/quad tangents too close to sort + TEST(skpnaoxrane_ru23), + TEST(skptcmevents_org23), + TEST(skpredbullskatearcade_es16), + TEST(skpfinanzasdigital_com9), TEST(skpgithub_io_26), TEST(skpgithub_io_25), TEST(skpwww_meb_gov_tr_5), diff --git a/tools/pathops_sorter.htm b/tools/pathops_sorter.htm index 41314f62a1..865cbbf755 100644 --- a/tools/pathops_sorter.htm +++ b/tools/pathops_sorter.htm @@ -1,4 +1,4 @@ -<!DOCTYPE html> +<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head> @@ -899,12 +899,69 @@ op intersect {{{-308.65463091760211, -549.4520029924679} -308.65463091760211, -569.4520029924679 </div> +<div id="skpwww_educationalcraft_com_4"> +{{{974.91998291015625, 1481.7769775390625}, {974.91998291015625, 1481.7760009765625}, {977.3189697265625, 1484.6190185546875}, {975.10699462890625, 1486.97802734375}}} +{{fX=974.91998291015625 fY=1481.7769775390625 }, {fX=974.92071342468262 fY=1481.7972941398621 }} } +</div> + +<div id="skpwww_educationalcraft_com_4a"> +{{{962.10699462890625, 1485.654052734375}, {962.10699462890625, 1485.654052734375}, {960.58502197265625, 1483.595947265625}, {957.53900146484375, 1482.0970458984375}}} +{{{963.21502685546875, 1486.6700439453125}, {962.7449951171875, 1486.6700439453125}, {962.10699462890625, 1485.654052734375}, {962.10699462890625, 1485.654052734375}}} +</div> + +<div id="skpwww_educationalcraft_com_4b"> +{{{980.9000244140625, 1474.3280029296875}, {980.9000244140625, 1474.3280029296875}, {978.89300537109375, 1471.95703125}, {981.791015625, 1469.487060546875}}} +{{{981.791015625, 1469.487060546875}, {981.791015625, 1469.4859619140625}, {983.3580322265625, 1472.72900390625}, {980.9000244140625, 1474.3280029296875}}} +</div> + +<div id="skpwww_aceinfographics_com_106"> +{{{168, 29.6722088f}, {166, 29.6773338f}}} +{{{166.878677f, 29.6750813f}, {167.388f, 29.6763878f}, {168.019989f, 29.6769352f}}} +</div> + +<div id="skpwww_tcmevents_org_13"> +{{{465.84668f, 547.288391f}, {467.274506f, 552.852356f}, {468.506836f, 560.718567f}}} +{{{468.506836f, 560.718567f}, {467.336121f, 553.24585f}, {465.951904f, 547.960144f}} +</div> + +<div id="skpwww_kitcheninspirations_wordpress_com_66"> +{{{60.8333359f, 27820.498f}, {47.1666679f, 27820.5f}}} +{{{60.8333359f, 27820.668f}, {60.8333359f, 27820.498f}}} +{{{47.1666679f, 27820.498f}, {60.8333359f, 27820.5f}}} +{{{60.8333359f, 27820.5f}, {60.8333359f, 27820.668f}}} +</div> + +<div id="skpwww_galaxystwo_com_4"> +{{{10105, 2510}, {10123, 2509.98999f}}} +{{{10105, 2509.98999f}, {10123, 2510}}} +</div> + +<div id="skpwww_wartepop_blogspot_com_br_6"> +{{{124.666672f, 152.333344f}, {125.909309f, 152.333344f}, {126.787994f, 153.309662f}}} +{{fX=124.66666412353516 fY=152.33334350585937 }, {fX=126.78799438476562 fY=153.30966186523437 }} } +{{fX=124.66666412353516 fY=152.33334350585937 }, {fX=127.02368927001953 fY=153.30966186523437 }} } +</div> + +<div id="skpwww_wartepop_blogspot_com_br_6a"> +{{{124.666672f, 152.333344f}, {125.909309f, 152.333344f}, {126.787994f, 153.309662f}}} +{{fX=124.66667175292969 fY=152.33334350585937 }, {fX=126.78799438476562 fY=153.30966186523437 }} } +{{fX=124.66667175292969 fY=152.33334350585937 }, {fX=127.02368927001953 fY=153.30966186523437 }} } +</div> </div> <script type="text/javascript"> var testDivs = [ + skpwww_wartepop_blogspot_com_br_6, + skpwww_wartepop_blogspot_com_br_6a, + skpwww_galaxystwo_com_4, + skpwww_kitcheninspirations_wordpress_com_66, + skpwww_tcmevents_org_13, + skpwww_aceinfographics_com_106, + skpwww_educationalcraft_com_4b, + skpwww_educationalcraft_com_4a, + skpwww_educationalcraft_com_4, cubicLineErr2, cubicLineErr1, cubicLineMiss1, diff --git a/tools/pathops_visualizer.htm b/tools/pathops_visualizer.htm new file mode 100644 index 0000000000..c7f7804fd9 --- /dev/null +++ b/tools/pathops_visualizer.htm @@ -0,0 +1,3925 @@ +<html> +<head> +<div height="0" hidden="true"> +<div id="rects4"> + RunTestSet [rects4] + +{{0,0}, {1,0}}, +{{1,0}, {1,1}}, +{{1,1}, {0,1}}, +{{0,1}, {0,0}}, +{{0,0}, {2,0}}, +{{2,0}, {2,2}}, +{{2,2}, {0,2}}, +{{0,2}, {0,0}}, +op difference +{{0,0}, {2,0}}, +{{2,0}, {2,2}}, +{{2,2}, {0,2}}, +{{0,2}, {0,0}}, +{{0,0}, {3,0}}, +{{3,0}, {3,3}}, +{{3,3}, {0,3}}, +{{0,3}, {0,0}}, +debugShowLineIntersection wtTs[0]=0 {{1,0}, {1,1}} {{1,0}} wnTs[0]=1 {{0,0}, {1,0}} +debugShowLineIntersection wtTs[0]=1 {{0,1}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {1,0}} +debugShowLineIntersection wtTs[0]=0 {{1,1}, {0,1}} {{1,1}} wnTs[0]=1 {{1,0}, {1,1}} +debugShowLineIntersection wtTs[0]=0 {{0,1}, {0,0}} {{0,1}} wnTs[0]=1 {{1,1}, {0,1}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {2,0}} {{0,0}} wtTs[1]=0.5 {{1,0}} wnTs[0]=0 {{0,0}, {1,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=1 {{0,2}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {1,0}} +debugShowLineIntersection wtTs[0]=0.5 {{0,0}, {2,0}} {{1,0}} wnTs[0]=0 {{1,0}, {1,1}} +debugShowLineIntersection wtTs[0]=0.5 {{0,2}, {0,0}} {{0,1}} wnTs[0]=1 {{1,1}, {0,1}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {2,0}} {{0,0}} wnTs[0]=1 {{0,1}, {0,0}} +debugShowLineIntersection wtTs[0]=0.5 {{0,2}, {0,0}} {{0,1}} wtTs[1]=1 {{0,0}} wnTs[0]=0 {{0,1}, {0,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{0,0}, {2,0}} {{0,0}} wtTs[1]=0.5 {{1,0}} wnTs[0]=0 {{0,0}, {1,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=1 {{0,2}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {1,0}} +debugShowLineIntersection wtTs[0]=0.5 {{0,0}, {2,0}} {{1,0}} wnTs[0]=0 {{1,0}, {1,1}} +debugShowLineIntersection wtTs[0]=0.5 {{0,2}, {0,0}} {{0,1}} wnTs[0]=1 {{1,1}, {0,1}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {2,0}} {{0,0}} wnTs[0]=1 {{0,1}, {0,0}} +debugShowLineIntersection wtTs[0]=0.5 {{0,2}, {0,0}} {{0,1}} wtTs[1]=1 {{0,0}} wnTs[0]=0 {{0,1}, {0,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{0,0}, {3,0}} {{0,0}} wtTs[1]=0.333333333 {{1,0}} wnTs[0]=0 {{0,0}, {1,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=1 {{0,3}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {1,0}} +debugShowLineIntersection wtTs[0]=0.333333333 {{0,0}, {3,0}} {{1,0}} wnTs[0]=0 {{1,0}, {1,1}} +debugShowLineIntersection wtTs[0]=0.666666667 {{0,3}, {0,0}} {{0,1}} wnTs[0]=1 {{1,1}, {0,1}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {3,0}} {{0,0}} wnTs[0]=1 {{0,1}, {0,0}} +debugShowLineIntersection wtTs[0]=0.666666667 {{0,3}, {0,0}} {{0,1}} wtTs[1]=1 {{0,0}} wnTs[0]=0 {{0,1}, {0,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{2,0}, {2,2}} {{2,0}} wnTs[0]=1 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=1 {{0,2}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=0 {{2,2}, {0,2}} {{2,2}} wnTs[0]=1 {{2,0}, {2,2}} +debugShowLineIntersection wtTs[0]=0 {{0,2}, {0,0}} {{0,2}} wnTs[0]=1 {{2,2}, {0,2}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {2,0}} {{0,0}} wtTs[1]=1 {{2,0}} wnTs[0]=0 {{0,0}, {2,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{2,0}, {2,2}} {{2,0}} wnTs[0]=1 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=1 {{0,2}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=1 {{0,0}, {2,0}} {{2,0}} wnTs[0]=0 {{2,0}, {2,2}} +debugShowLineIntersection wtTs[0]=0 {{2,0}, {2,2}} {{2,0}} wtTs[1]=1 {{2,2}} wnTs[0]=0 {{2,0}, {2,2}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{2,2}, {0,2}} {{2,2}} wnTs[0]=1 {{2,0}, {2,2}} +debugShowLineIntersection wtTs[0]=1 {{2,0}, {2,2}} {{2,2}} wnTs[0]=0 {{2,2}, {0,2}} +debugShowLineIntersection wtTs[0]=0 {{2,2}, {0,2}} {{2,2}} wtTs[1]=1 {{0,2}} wnTs[0]=0 {{2,2}, {0,2}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{0,2}, {0,0}} {{0,2}} wnTs[0]=1 {{2,2}, {0,2}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {2,0}} {{0,0}} wnTs[0]=1 {{0,2}, {0,0}} +debugShowLineIntersection wtTs[0]=1 {{2,2}, {0,2}} {{0,2}} wnTs[0]=0 {{0,2}, {0,0}} +debugShowLineIntersection wtTs[0]=0 {{0,2}, {0,0}} {{0,2}} wtTs[1]=1 {{0,0}} wnTs[0]=0 {{0,2}, {0,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{0,0}, {3,0}} {{0,0}} wtTs[1]=0.666666667 {{2,0}} wnTs[0]=0 {{0,0}, {2,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=1 {{0,3}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=0.666666667 {{0,0}, {3,0}} {{2,0}} wnTs[0]=0 {{2,0}, {2,2}} +debugShowLineIntersection wtTs[0]=0.333333333 {{0,3}, {0,0}} {{0,2}} wnTs[0]=1 {{2,2}, {0,2}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {3,0}} {{0,0}} wnTs[0]=1 {{0,2}, {0,0}} +debugShowLineIntersection wtTs[0]=0.333333333 {{0,3}, {0,0}} {{0,2}} wtTs[1]=1 {{0,0}} wnTs[0]=0 {{0,2}, {0,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{2,0}, {2,2}} {{2,0}} wnTs[0]=1 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=1 {{0,2}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=0 {{2,2}, {0,2}} {{2,2}} wnTs[0]=1 {{2,0}, {2,2}} +debugShowLineIntersection wtTs[0]=0 {{0,2}, {0,0}} {{0,2}} wnTs[0]=1 {{2,2}, {0,2}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {3,0}} {{0,0}} wtTs[1]=0.666666667 {{2,0}} wnTs[0]=0 {{0,0}, {2,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=1 {{0,3}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=0.666666667 {{0,0}, {3,0}} {{2,0}} wnTs[0]=0 {{2,0}, {2,2}} +debugShowLineIntersection wtTs[0]=0.333333333 {{0,3}, {0,0}} {{0,2}} wnTs[0]=1 {{2,2}, {0,2}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {3,0}} {{0,0}} wnTs[0]=1 {{0,2}, {0,0}} +debugShowLineIntersection wtTs[0]=0.333333333 {{0,3}, {0,0}} {{0,2}} wtTs[1]=1 {{0,0}} wnTs[0]=0 {{0,2}, {0,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{3,0}, {3,3}} {{3,0}} wnTs[0]=1 {{0,0}, {3,0}} +debugShowLineIntersection wtTs[0]=1 {{0,3}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {3,0}} +debugShowLineIntersection wtTs[0]=0 {{3,3}, {0,3}} {{3,3}} wnTs[0]=1 {{3,0}, {3,3}} +debugShowLineIntersection wtTs[0]=0 {{0,3}, {0,0}} {{0,3}} wnTs[0]=1 {{3,3}, {0,3}} +SkOpSegment::debugShowTs - id=0 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=1 t=1 1,0 w=1 o=0] +SkOpSegment::debugShowTs o id=4 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] +SkOpSegment::addTPair addTPair this=4 0.5 other=0 1 +SkOpSegment::debugShowTs + id=0 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=4,1 t=1 1,0 w=1 o=0] +SkOpSegment::debugShowTs o id=4 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs - id=3 [o=2 t=0 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=7 [o=10,6 t=0 0,2 w=1 o=0] [o=2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::addTPair addTPair this=3 0 other=7 0.5 +SkOpSegment::debugShowTs + id=3 [o=7,2 t=0 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=7 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs - id=0 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=4,1 t=1 1,0 w=1 o=0] +SkOpSegment::debugShowTs o id=8 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=8 0.5 other=0 1 +SkOpSegment::debugShowTs + id=0 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=8,4,1 t=1 1,0 w=1 o=0] +SkOpSegment::debugShowTs o id=8 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=3 [o=7,2 t=0 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=11 [o=10,6 t=0 0,2 w=1 o=0] [o=2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=3 0 other=11 0.5 +SkOpSegment::debugShowTs + id=3 [o=11,7,2 t=0 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=11 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=0 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=8,4,1 t=1 1,0 w=1 o=0] +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=1 t=0.333 1,0 w=1 o=0] [o=9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=12 0.333333333 other=0 1 +SkOpSegment::debugShowTs + id=0 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=12,8,4,1 t=1 1,0 w=1 o=0] +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.333 1,0 w=1 o=0] [o=9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=3 [o=11,7,2 t=0 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=10,6 t=0.333 0,2 w=1 o=0] [o=2 t=0.667 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=3 0 other=15 0.666666667 +SkOpSegment::debugShowTs + id=3 [o=15,11,7,2 t=0 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=10,6 t=0.333 0,2 w=1 o=0] [o=3,2 t=0.667 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=4 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs o id=8 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::debugShowTs + id=4 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs o id=8 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=5 [o=12,8,4 t=0 2,0 w=1 o=0] [o=10,6 t=1 2,2 w=1 o=0] +SkOpSegment::debugShowTs o id=9 [o=12,8,4 t=0 2,0 w=1 o=0] [o=10,6 t=1 2,2 w=1 o=0] operand +SkOpSegment::debugShowTs + id=5 [o=12,8,4 t=0 2,0 w=1 o=0] [o=10,6 t=1 2,2 w=1 o=0] +SkOpSegment::debugShowTs o id=9 [o=12,8,4 t=0 2,0 w=1 o=0] [o=10,6 t=1 2,2 w=1 o=0] operand +SkOpSegment::debugShowTs - id=6 [o=9,5 t=0 2,2 w=1 o=0] [o=15,11,7 t=1 0,2 w=1 o=0] +SkOpSegment::debugShowTs o id=10 [o=9,5 t=0 2,2 w=1 o=0] [o=15,11,7 t=1 0,2 w=1 o=0] operand +SkOpSegment::debugShowTs + id=6 [o=9,5 t=0 2,2 w=1 o=0] [o=15,11,7 t=1 0,2 w=1 o=0] +SkOpSegment::debugShowTs o id=10 [o=9,5 t=0 2,2 w=1 o=0] [o=15,11,7 t=1 0,2 w=1 o=0] operand +SkOpSegment::debugShowTs - id=7 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=11 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs + id=7 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=11 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=4 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.333 1,0 w=1 o=0] [o=9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=12 0.666666667 other=4 1 +SkOpSegment::debugShowTs + id=4 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=12,9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.333 1,0 w=1 o=0] [o=4,9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=7 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=10,6 t=0.333 0,2 w=1 o=0] [o=3,2 t=0.667 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=7 0 other=15 0.333333333 +SkOpSegment::debugShowTs + id=7 [o=15,10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=7,10,6 t=0.333 0,2 w=1 o=0] [o=3,2 t=0.667 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=8 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.333 1,0 w=1 o=0] [o=4,9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=12 0.666666667 other=8 1 +SkOpSegment::debugShowTs + id=8 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=12,9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.333 1,0 w=1 o=0] [o=8,4,9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=11 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=7,10,6 t=0.333 0,2 w=1 o=0] [o=3,2 t=0.667 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=11 0 other=15 0.333333333 +SkOpSegment::debugShowTs + id=11 [o=15,10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=11,7,10,6 t=0.333 0,2 w=1 o=0] [o=3,2 t=0.667 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpContour::calcCoincidentWinding count=6 +SkOpSegment::debugShowTs p id=0 [o=15,11,7,3 t=0 0,0 w=0 o=0] [o=12,8,4,1 t=1 1,0 w=1 o=0] done +SkOpSegment::debugShowTs o id=4 [o=15,11,7,3 t=0 0,0 w=0 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=12,9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs p id=3 [o=15,11,7,2 t=0 0,1 w=0 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] done +SkOpSegment::debugShowTs o id=7 [o=15,10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=0 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpContour::calcCoincidentWinding count=6 +SkOpSegment::debugShowTs p id=4 [o=15,11,7,3 t=0 0,0 w=0 o=0] [o=0,1 t=0.5 1,0 w=1 o=1] [o=12,9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs o id=8 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=0 o=0] [o=12,9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::debugShowTs p id=5 [o=12,8,4 t=0 2,0 w=1 o=1] [o=10,6 t=1 2,2 w=1 o=0] +SkOpSegment::debugShowTs o id=9 [o=12,8,4 t=0 2,0 w=0 o=0] [o=10,6 t=1 2,2 w=1 o=0] operand done +SkOpSegment::debugShowTs p id=6 [o=9,5 t=0 2,2 w=1 o=1] [o=15,11,7 t=1 0,2 w=1 o=0] +SkOpSegment::debugShowTs o id=10 [o=9,5 t=0 2,2 w=0 o=0] [o=15,11,7 t=1 0,2 w=1 o=0] operand done +SkOpSegment::debugShowTs p id=7 [o=15,10,6 t=0 0,2 w=1 o=1] [o=3,2 t=0.5 0,1 w=0 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=11 [o=15,10,6 t=0 0,2 w=0 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs p id=4 [o=15,11,7,3 t=0 0,0 w=0 o=0] [o=0,1 t=0.5 1,0 w=1 o=2] [o=12,9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.333 1,0 w=0 o=0] [o=8,4,9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::debugShowTs p id=7 [o=15,10,6 t=0 0,2 w=1 o=2] [o=3,2 t=0.5 0,1 w=0 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=11,7,10,6 t=0.333 0,2 w=0 o=0] [o=3,2 t=0.667 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpContour::calcCoincidentWinding count=2 +SkOpSegment::debugShowTs p id=8 [o=15,11,7,3 t=0 0,0 w=2 o=0] [o=0,1 t=0.5 1,0 w=0 o=0] [o=12,9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=0 o=0] [o=0,1 t=0.333 1,0 w=0 o=0] [o=8,4,9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::debugShowTs p id=11 [o=15,10,6 t=0 0,2 w=0 o=0] [o=3,2 t=0.5 0,1 w=2 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=11,7,10,6 t=0.333 0,2 w=0 o=0] [o=3,2 t=0.667 0,1 w=0 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=12 0.333333333 other=8 0.5 +SkOpSegment::addTPair addTPair this=12 0.333333333 other=4 0.5 +SkOpSegment::addTPair addTPair duplicate this=8 0.5 other=12 0.333333333 +SkOpSegment::addTPair addTPair this=8 0.5 other=4 0.5 +SkOpSegment::addTPair addTPair duplicate this=4 0.5 other=8 0.5 +SkOpSegment::addTPair addTPair duplicate this=4 0.5 other=12 0.333333333 +SkOpSegment::addTPair addTPair this=15 0.666666667 other=11 0.5 +SkOpSegment::addTPair addTPair this=15 0.666666667 other=7 0.5 +SkOpSegment::addTPair addTPair duplicate this=11 0.5 other=15 0.666666667 +SkOpSegment::addTPair addTPair this=11 0.5 other=7 0.5 +SkOpSegment::addTPair addTPair duplicate this=7 0.5 other=11 0.5 +SkOpSegment::addTPair addTPair duplicate this=7 0.5 other=15 0.666666667 +SkOpSegment::addTPair addTPair this=4 1 other=8 1 +SkOpSegment::addTPair addTPair this=5 0 other=9 0 +SkOpSegment::addTPair addTPair duplicate this=5 0 other=9 0 +SkOpSegment::addTPair addTPair duplicate this=5 0 other=9 0 +SkOpSegment::addTPair addTPair this=6 1 other=10 1 +SkOpSegment::addTPair addTPair this=7 0 other=11 0 +SkOpSegment::addTPair addTPair duplicate this=7 0 other=11 0 +SkOpSegment::addTPair addTPair duplicate this=7 0 other=11 0 +SkOpContour::joinCoincidence count=6 +SkOpContour::joinCoincidence count=6 +SkOpContour::joinCoincidence count=2 +SkOpSegment::sortAngles [1] tStart=0 [1] +SkOpAngle::after [1/1] 23/23 tStart=0 tEnd=1 < [4/1] 31/31 tStart=0.5 tEnd=1 < [8/2] 15/15 tStart=0.5 tEnd=0 T 4 +SkOpSegment::sortAngles [2] tStart=1 [2] +SkOpAngle::after [2/1] 31/31 tStart=1 tEnd=0 < [7/2] 23/23 tStart=0.5 tEnd=0 < [11/1] 7/7 tStart=0.5 tEnd=1 F 4 +SkOpSegment::sortAngles [4] tStart=1 [9] +SkOpAngle::after [4/2] 15/15 tStart=1 tEnd=0.5 < [5/1] 23/23 tStart=0 tEnd=1 < [12/1] 31/31 tStart=0.666666667 tEnd=1 T 4 +SkOpSegment::sortAngles [5] tStart=1 [5] +SkOpSegment::sortAngles [6] tStart=1 [3] +SkOpAngle::after [6/2] 31/31 tStart=1 tEnd=0 < [7/1] 7/7 tStart=0 tEnd=0.5 < [15/1] 23/23 tStart=0.333333333 tEnd=0 T 4 +SkOpSegment::sortAngles [8] tStart=0 [1] +SkOpSegment::debugShowActiveSpans id=1 (1,0 1,1) t=0 (1,0) tEnd=1 other=12 otherT=0.333333333 otherIndex=7 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=2 (1,1 0,1) t=0 (1,1) tEnd=1 other=1 otherT=1 otherIndex=4 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=4 (0,0 2,0) t=0.5 (1,0) tEnd=1 other=8 otherT=0.5 otherIndex=4 windSum=? windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=5 (2,0 2,2) t=0 (2,0) tEnd=1 other=9 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=6 (2,2 0,2) t=0 (2,2) tEnd=1 other=9 otherT=1 otherIndex=5 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=7 (0,2 0,0) t=0 (0,2) tEnd=0.5 other=11 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=8 (0,0 2,0) t=0 (0,0) tEnd=0.5 other=15 otherT=1 otherIndex=10 windSum=? windValue=2 oppValue=0 +SkOpSegment::debugShowActiveSpans id=11 (0,2 0,0) t=0.5 (0,1) tEnd=1 other=7 otherT=0.5 otherIndex=4 windSum=? windValue=2 oppValue=0 +SkOpSegment::debugShowActiveSpans id=12 (0,0 3,0) t=0.666666667 (2,0) tEnd=1 other=8 otherT=1 otherIndex=9 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=13 (3,0 3,3) t=0 (3,0) tEnd=1 other=12 otherT=1 otherIndex=12 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=14 (3,3 0,3) t=0 (3,3) tEnd=1 other=13 otherT=1 otherIndex=1 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=15 (0,3 0,0) t=0 (0,3) tEnd=0.333333333 other=14 otherT=1 otherIndex=1 windSum=? windValue=1 oppValue=0 +SkOpSegment::findTop +SkOpAngle::dumpOne [11/2] next=8/1 sect=23/23 s=1 [11] e=0.5 [7] sgn=1 windVal=2 windSum=? operand +SkOpAngle::dumpOne [8/1] next=11/2 sect=31/31 s=0 [0] e=0.5 [4] sgn=-1 windVal=2 windSum=? operand stop +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [6] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 oppValue=0 +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [5] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 oppValue=0 +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [4] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 oppValue=0 +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [7] (0,1) tEnd=1 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 oppValue=0 +SkOpSegment::nextChase mismatched signs +SkOpSegment::markWinding id=8 (0,0 2,0) t=0 [0] (0,0) tEnd=0 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 oppValue=0 +SkOpSegment::markWinding id=8 (0,0 2,0) t=0 [1] (0,0) tEnd=0 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 oppValue=0 +SkOpSegment::markWinding id=8 (0,0 2,0) t=0 [2] (0,0) tEnd=0 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 oppValue=0 +SkOpSegment::markWinding id=8 (0,0 2,0) t=0 [3] (0,0) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 oppValue=0 +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [6] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 oppValue=0 +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [5] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 oppValue=0 +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [4] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 oppValue=0 +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [7] (0,1) tEnd=1 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 oppValue=0 +SkOpSegment::activeOp id=11 t=0.5 tEnd=1 op=diff miFrom=0 miTo=0 suFrom=1 suTo=0 result=0 +SkOpSegment::markDoneBinary id=11 (0,2 0,0) t=0.5 [6] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 oppValue=0 +SkOpSegment::markDoneBinary id=11 (0,2 0,0) t=0.5 [5] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 oppValue=0 +SkOpSegment::markDoneBinary id=11 (0,2 0,0) t=0.5 [4] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 oppValue=0 +SkOpSegment::markDoneBinary id=11 (0,2 0,0) t=0.5 [7] (0,1) tEnd=1 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 oppValue=0 +SkOpSegment::nextChase mismatched signs +SkOpSegment::markDoneBinary id=8 (0,0 2,0) t=0 [0] (0,0) tEnd=0 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 oppValue=0 +SkOpSegment::markDoneBinary id=8 (0,0 2,0) t=0 [1] (0,0) tEnd=0 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 oppValue=0 +SkOpSegment::markDoneBinary id=8 (0,0 2,0) t=0 [2] (0,0) tEnd=0 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 oppValue=0 +SkOpSegment::markDoneBinary id=8 (0,0 2,0) t=0 [3] (0,0) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 oppValue=0 +bridgeOp chase.append id=8 windSum=-2147483647 small=0 +SkOpSegment::markWinding id=1 (1,0 1,1) t=0 [0] (1,0) tEnd=0 newWindSum=1 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=0 +SkOpSegment::markWinding id=1 (1,0 1,1) t=0 [1] (1,0) tEnd=0 newWindSum=1 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=0 +SkOpSegment::markWinding id=1 (1,0 1,1) t=0 [2] (1,0) tEnd=0 newWindSum=1 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=0 +SkOpSegment::markWinding id=1 (1,0 1,1) t=0 [3] (1,0) tEnd=1 newWindSum=1 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=0 +SkOpSegment::markWinding id=2 (1,1 0,1) t=0 [0] (1,1) tEnd=1 newWindSum=1 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=0 +SkOpSegment::markAngle last id=2 windSum=? small=0 +SkOpSegment::markWinding id=4 (0,0 2,0) t=0.5 [4] (1,0) tEnd=0.5 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=2 +SkOpSegment::markWinding id=4 (0,0 2,0) t=0.5 [5] (1,0) tEnd=0.5 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=2 +SkOpSegment::markWinding id=4 (0,0 2,0) t=0.5 [6] (1,0) tEnd=0.5 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=2 +SkOpSegment::markWinding id=4 (0,0 2,0) t=0.5 [7] (1,0) tEnd=1 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=2 +SkOpSegment::markAngle last id=4 windSum=? small=0 +SkOpSegment::debugShowActiveSpans id=1 (1,0 1,1) t=0 (1,0) tEnd=1 other=12 otherT=0.333333333 otherIndex=7 windSum=1 windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=2 (1,1 0,1) t=0 (1,1) tEnd=1 other=1 otherT=1 otherIndex=4 windSum=1 windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=4 (0,0 2,0) t=0.5 (1,0) tEnd=1 other=8 otherT=0.5 otherIndex=4 windSum=2 windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=5 (2,0 2,2) t=0 (2,0) tEnd=1 other=9 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=6 (2,2 0,2) t=0 (2,2) tEnd=1 other=9 otherT=1 otherIndex=5 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=7 (0,2 0,0) t=0 (0,2) tEnd=0.5 other=11 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=12 (0,0 3,0) t=0.666666667 (2,0) tEnd=1 other=8 otherT=1 otherIndex=9 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=13 (3,0 3,3) t=0 (3,0) tEnd=1 other=12 otherT=1 otherIndex=12 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=14 (3,3 0,3) t=0 (3,3) tEnd=1 other=13 otherT=1 otherIndex=1 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=15 (0,3 0,0) t=0 (0,3) tEnd=0.333333333 other=14 otherT=1 otherIndex=1 windSum=? windValue=1 oppValue=0 +SkOpSegment::activeOp id=1 t=0 tEnd=1 op=diff miFrom=0 miTo=1 suFrom=1 suTo=1 result=0 +SkOpSegment::markDoneBinary id=1 (1,0 1,1) t=0 [0] (1,0) tEnd=0 newWindSum=1 newOppSum=-2 oppSum=-2 windSum=1 windValue=1 oppValue=0 +SkOpSegment::markDoneBinary id=1 (1,0 1,1) t=0 [1] (1,0) tEnd=0 newWindSum=1 newOppSum=-2 oppSum=-2 windSum=1 windValue=1 oppValue=0 +SkOpSegment::markDoneBinary id=1 (1,0 1,1) t=0 [2] (1,0) tEnd=0 newWindSum=1 newOppSum=-2 oppSum=-2 windSum=1 windValue=1 oppValue=0 +SkOpSegment::markDoneBinary id=1 (1,0 1,1) t=0 [3] (1,0) tEnd=1 newWindSum=1 newOppSum=-2 oppSum=-2 windSum=1 windValue=1 oppValue=0 +SkOpSegment::markDoneBinary id=2 (1,1 0,1) t=0 [0] (1,1) tEnd=1 newWindSum=1 newOppSum=-2 oppSum=-2 windSum=1 windValue=1 oppValue=0 +bridgeOp chase.append id=2 windSum=-2147483647 small=0 +SkOpSegment::markWinding id=7 (0,2 0,0) t=0 [0] (0,2) tEnd=0 newWindSum=-1 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=2 +SkOpSegment::markWinding id=7 (0,2 0,0) t=0 [1] (0,2) tEnd=0 newWindSum=-1 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=2 +SkOpSegment::markWinding id=7 (0,2 0,0) t=0 [2] (0,2) tEnd=0 newWindSum=-1 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=2 +SkOpSegment::markWinding id=7 (0,2 0,0) t=0 [3] (0,2) tEnd=0.5 newWindSum=-1 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=2 +SkOpSegment::markAngle last id=7 windSum=-1 small=0 +SkOpSegment::debugShowActiveSpans id=4 (0,0 2,0) t=0.5 (1,0) tEnd=1 other=8 otherT=0.5 otherIndex=4 windSum=2 windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=5 (2,0 2,2) t=0 (2,0) tEnd=1 other=9 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=6 (2,2 0,2) t=0 (2,2) tEnd=1 other=9 otherT=1 otherIndex=5 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=7 (0,2 0,0) t=0 (0,2) tEnd=0.5 other=11 otherT=0 otherIndex=0 windSum=-1 windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=12 (0,0 3,0) t=0.666666667 (2,0) tEnd=1 other=8 otherT=1 otherIndex=9 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=13 (3,0 3,3) t=0 (3,0) tEnd=1 other=12 otherT=1 otherIndex=12 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=14 (3,3 0,3) t=0 (3,3) tEnd=1 other=13 otherT=1 otherIndex=1 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=15 (0,3 0,0) t=0 (0,3) tEnd=0.333333333 other=14 otherT=1 otherIndex=1 windSum=? windValue=1 oppValue=0 +SkOpSegment::activeOp id=7 t=0.5 tEnd=0 op=diff miFrom=0 miTo=1 suFrom=0 suTo=1 result=0 +SkOpSegment::markDoneBinary id=7 (0,2 0,0) t=0 [0] (0,2) tEnd=0 newWindSum=-1 newOppSum=-2 oppSum=-2 windSum=-1 windValue=1 oppValue=2 +SkOpSegment::markDoneBinary id=7 (0,2 0,0) t=0 [1] (0,2) tEnd=0 newWindSum=-1 newOppSum=-2 oppSum=-2 windSum=-1 windValue=1 oppValue=2 +SkOpSegment::markDoneBinary id=7 (0,2 0,0) t=0 [2] (0,2) tEnd=0 newWindSum=-1 newOppSum=-2 oppSum=-2 windSum=-1 windValue=1 oppValue=2 +SkOpSegment::markDoneBinary id=7 (0,2 0,0) t=0 [3] (0,2) tEnd=0.5 newWindSum=-1 newOppSum=-2 oppSum=-2 windSum=-1 windValue=1 oppValue=2 +bridgeOp chase.append id=7 windSum=-1 small=0 +SkOpSegment::markWinding id=15 (0,3 0,0) t=0 [0] (0,3) tEnd=0.333333333 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 oppValue=0 +SkOpSegment::markWinding id=14 (3,3 0,3) t=0 [0] (3,3) tEnd=1 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 oppValue=0 +SkOpSegment::markWinding id=13 (3,0 3,3) t=0 [0] (3,0) tEnd=1 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 oppValue=0 +SkOpSegment::markWinding id=12 (0,0 3,0) t=0.666666667 [10] (2,0) tEnd=0.666666667 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 oppValue=0 +SkOpSegment::markWinding id=12 (0,0 3,0) t=0.666666667 [9] (2,0) tEnd=0.666666667 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 oppValue=0 +SkOpSegment::markWinding id=12 (0,0 3,0) t=0.666666667 [8] (2,0) tEnd=0.666666667 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 oppValue=0 +SkOpSegment::markWinding id=12 (0,0 3,0) t=0.666666667 [11] (2,0) tEnd=1 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 oppValue=0 +SkOpSegment::markAngle last id=12 windSum=-1 small=0 +SkOpSegment::markWinding id=6 (2,2 0,2) t=0 [0] (2,2) tEnd=0 newWindSum=-1 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=1 +SkOpSegment::markWinding id=6 (2,2 0,2) t=0 [1] (2,2) tEnd=1 newWindSum=-1 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=1 +SkOpSegment::nextChase mismatched signs +SkOpSegment::markAngle last id=6 windSum=-1 small=0 +SkOpSegment::debugShowActiveSpans id=4 (0,0 2,0) t=0.5 (1,0) tEnd=1 other=8 otherT=0.5 otherIndex=4 windSum=2 windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=5 (2,0 2,2) t=0 (2,0) tEnd=1 other=9 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=6 (2,2 0,2) t=0 (2,2) tEnd=1 other=9 otherT=1 otherIndex=5 windSum=-1 windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=12 (0,0 3,0) t=0.666666667 (2,0) tEnd=1 other=8 otherT=1 otherIndex=9 windSum=-1 windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=13 (3,0 3,3) t=0 (3,0) tEnd=1 other=12 otherT=1 otherIndex=12 windSum=-1 windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=14 (3,3 0,3) t=0 (3,3) tEnd=1 other=13 otherT=1 otherIndex=1 windSum=-1 windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=15 (0,3 0,0) t=0 (0,3) tEnd=0.333333333 other=14 otherT=1 otherIndex=1 windSum=-1 windValue=1 oppValue=0 +SkOpSegment::activeOp id=15 t=0.333333333 tEnd=0 op=diff miFrom=0 miTo=0 suFrom=0 suTo=1 result=0 +SkOpSegment::markDoneBinary id=15 (0,3 0,0) t=0 [0] (0,3) tEnd=0.333333333 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 oppValue=0 +SkOpSegment::markDoneBinary id=14 (3,3 0,3) t=0 [0] (3,3) tEnd=1 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 oppValue=0 +SkOpSegment::markDoneBinary id=13 (3,0 3,3) t=0 [0] (3,0) tEnd=1 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 oppValue=0 +SkOpSegment::markDoneBinary id=12 (0,0 3,0) t=0.666666667 [10] (2,0) tEnd=0.666666667 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 oppValue=0 +SkOpSegment::markDoneBinary id=12 (0,0 3,0) t=0.666666667 [9] (2,0) tEnd=0.666666667 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 oppValue=0 +SkOpSegment::markDoneBinary id=12 (0,0 3,0) t=0.666666667 [8] (2,0) tEnd=0.666666667 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 oppValue=0 +SkOpSegment::markDoneBinary id=12 (0,0 3,0) t=0.666666667 [11] (2,0) tEnd=1 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 oppValue=0 +bridgeOp chase.append id=12 windSum=-1 small=0 +SkOpSegment::debugShowActiveSpans id=4 (0,0 2,0) t=0.5 (1,0) tEnd=1 other=8 otherT=0.5 otherIndex=4 windSum=2 windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=5 (2,0 2,2) t=0 (2,0) tEnd=1 other=9 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=6 (2,2 0,2) t=0 (2,2) tEnd=1 other=9 otherT=1 otherIndex=5 windSum=-1 windValue=1 oppValue=1 +SkOpSegment::activeOp id=4 t=1 tEnd=0.5 op=diff miFrom=0 miTo=1 suFrom=0 suTo=1 result=0 +SkOpSegment::markDoneBinary id=4 (0,0 2,0) t=0.5 [6] (1,0) tEnd=0.5 newWindSum=2 newOppSum=-2 oppSum=-2 windSum=2 windValue=1 oppValue=2 +SkOpSegment::markDoneBinary id=4 (0,0 2,0) t=0.5 [5] (1,0) tEnd=0.5 newWindSum=2 newOppSum=-2 oppSum=-2 windSum=2 windValue=1 oppValue=2 +SkOpSegment::markDoneBinary id=4 (0,0 2,0) t=0.5 [4] (1,0) tEnd=0.5 newWindSum=2 newOppSum=-2 oppSum=-2 windSum=2 windValue=1 oppValue=2 +SkOpSegment::markDoneBinary id=4 (0,0 2,0) t=0.5 [7] (1,0) tEnd=1 newWindSum=2 newOppSum=-2 oppSum=-2 windSum=2 windValue=1 oppValue=2 +bridgeOp chase.append id=4 windSum=2 small=0 +SkOpSegment::markWinding id=5 (2,0 2,2) t=0 [0] (2,0) tEnd=0 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=1 +SkOpSegment::markWinding id=5 (2,0 2,2) t=0 [1] (2,0) tEnd=0 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=1 +SkOpSegment::markWinding id=5 (2,0 2,2) t=0 [2] (2,0) tEnd=0 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=1 +SkOpSegment::markWinding id=5 (2,0 2,2) t=0 [3] (2,0) tEnd=1 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 oppValue=1 +SkOpSegment::nextChase mismatched signs +</div> + +<div id="refRects4"> + RunTestSet [rects4] + +{{0,0}, {1,0}}, +{{1,0}, {1,1}}, +{{1,1}, {0,1}}, +{{0,1}, {0,0}}, +{{0,0}, {2,0}}, +{{2,0}, {2,2}}, +{{2,2}, {0,2}}, +{{0,2}, {0,0}}, +op difference +{{0,0}, {2,0}}, +{{2,0}, {2,2}}, +{{2,2}, {0,2}}, +{{0,2}, {0,0}}, +{{0,0}, {3,0}}, +{{3,0}, {3,3}}, +{{3,3}, {0,3}}, +{{0,3}, {0,0}}, +debugShowLineIntersection wtTs[0]=0 {{1,0}, {1,1}} {{1,0}} wnTs[0]=1 {{0,0}, {1,0}} +debugShowLineIntersection wtTs[0]=1 {{0,1}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {1,0}} +debugShowLineIntersection wtTs[0]=0 {{1,1}, {0,1}} {{1,1}} wnTs[0]=1 {{1,0}, {1,1}} +debugShowLineIntersection wtTs[0]=0 {{0,1}, {0,0}} {{0,1}} wnTs[0]=1 {{1,1}, {0,1}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {2,0}} {{0,0}} wtTs[1]=0.5 {{1,0}} wnTs[0]=0 {{0,0}, {1,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=1 {{0,2}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {1,0}} +debugShowLineIntersection wtTs[0]=0.5 {{0,0}, {2,0}} {{1,0}} wnTs[0]=0 {{1,0}, {1,1}} +debugShowLineIntersection wtTs[0]=0.5 {{0,2}, {0,0}} {{0,1}} wnTs[0]=1 {{1,1}, {0,1}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {2,0}} {{0,0}} wnTs[0]=1 {{0,1}, {0,0}} +debugShowLineIntersection wtTs[0]=0.5 {{0,2}, {0,0}} {{0,1}} wtTs[1]=1 {{0,0}} wnTs[0]=0 {{0,1}, {0,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{0,0}, {2,0}} {{0,0}} wtTs[1]=0.5 {{1,0}} wnTs[0]=0 {{0,0}, {1,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=1 {{0,2}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {1,0}} +debugShowLineIntersection wtTs[0]=0.5 {{0,0}, {2,0}} {{1,0}} wnTs[0]=0 {{1,0}, {1,1}} +debugShowLineIntersection wtTs[0]=0.5 {{0,2}, {0,0}} {{0,1}} wnTs[0]=1 {{1,1}, {0,1}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {2,0}} {{0,0}} wnTs[0]=1 {{0,1}, {0,0}} +debugShowLineIntersection wtTs[0]=0.5 {{0,2}, {0,0}} {{0,1}} wtTs[1]=1 {{0,0}} wnTs[0]=0 {{0,1}, {0,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{0,0}, {3,0}} {{0,0}} wtTs[1]=0.333333333 {{1,0}} wnTs[0]=0 {{0,0}, {1,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=1 {{0,3}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {1,0}} +debugShowLineIntersection wtTs[0]=0.333333333 {{0,0}, {3,0}} {{1,0}} wnTs[0]=0 {{1,0}, {1,1}} +debugShowLineIntersection wtTs[0]=0.666666667 {{0,3}, {0,0}} {{0,1}} wnTs[0]=1 {{1,1}, {0,1}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {3,0}} {{0,0}} wnTs[0]=1 {{0,1}, {0,0}} +debugShowLineIntersection wtTs[0]=0.666666667 {{0,3}, {0,0}} {{0,1}} wtTs[1]=1 {{0,0}} wnTs[0]=0 {{0,1}, {0,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{2,0}, {2,2}} {{2,0}} wnTs[0]=1 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=1 {{0,2}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=0 {{2,2}, {0,2}} {{2,2}} wnTs[0]=1 {{2,0}, {2,2}} +debugShowLineIntersection wtTs[0]=0 {{0,2}, {0,0}} {{0,2}} wnTs[0]=1 {{2,2}, {0,2}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {2,0}} {{0,0}} wtTs[1]=1 {{2,0}} wnTs[0]=0 {{0,0}, {2,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{2,0}, {2,2}} {{2,0}} wnTs[0]=1 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=1 {{0,2}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=1 {{0,0}, {2,0}} {{2,0}} wnTs[0]=0 {{2,0}, {2,2}} +debugShowLineIntersection wtTs[0]=0 {{2,0}, {2,2}} {{2,0}} wtTs[1]=1 {{2,2}} wnTs[0]=0 {{2,0}, {2,2}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{2,2}, {0,2}} {{2,2}} wnTs[0]=1 {{2,0}, {2,2}} +debugShowLineIntersection wtTs[0]=1 {{2,0}, {2,2}} {{2,2}} wnTs[0]=0 {{2,2}, {0,2}} +debugShowLineIntersection wtTs[0]=0 {{2,2}, {0,2}} {{2,2}} wtTs[1]=1 {{0,2}} wnTs[0]=0 {{2,2}, {0,2}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{0,2}, {0,0}} {{0,2}} wnTs[0]=1 {{2,2}, {0,2}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {2,0}} {{0,0}} wnTs[0]=1 {{0,2}, {0,0}} +debugShowLineIntersection wtTs[0]=1 {{2,2}, {0,2}} {{0,2}} wnTs[0]=0 {{0,2}, {0,0}} +debugShowLineIntersection wtTs[0]=0 {{0,2}, {0,0}} {{0,2}} wtTs[1]=1 {{0,0}} wnTs[0]=0 {{0,2}, {0,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{0,0}, {3,0}} {{0,0}} wtTs[1]=0.666666667 {{2,0}} wnTs[0]=0 {{0,0}, {2,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=1 {{0,3}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=0.666666667 {{0,0}, {3,0}} {{2,0}} wnTs[0]=0 {{2,0}, {2,2}} +debugShowLineIntersection wtTs[0]=0.333333333 {{0,3}, {0,0}} {{0,2}} wnTs[0]=1 {{2,2}, {0,2}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {3,0}} {{0,0}} wnTs[0]=1 {{0,2}, {0,0}} +debugShowLineIntersection wtTs[0]=0.333333333 {{0,3}, {0,0}} {{0,2}} wtTs[1]=1 {{0,0}} wnTs[0]=0 {{0,2}, {0,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{2,0}, {2,2}} {{2,0}} wnTs[0]=1 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=1 {{0,2}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=0 {{2,2}, {0,2}} {{2,2}} wnTs[0]=1 {{2,0}, {2,2}} +debugShowLineIntersection wtTs[0]=0 {{0,2}, {0,0}} {{0,2}} wnTs[0]=1 {{2,2}, {0,2}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {3,0}} {{0,0}} wtTs[1]=0.666666667 {{2,0}} wnTs[0]=0 {{0,0}, {2,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=1 {{0,3}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {2,0}} +debugShowLineIntersection wtTs[0]=0.666666667 {{0,0}, {3,0}} {{2,0}} wnTs[0]=0 {{2,0}, {2,2}} +debugShowLineIntersection wtTs[0]=0.333333333 {{0,3}, {0,0}} {{0,2}} wnTs[0]=1 {{2,2}, {0,2}} +debugShowLineIntersection wtTs[0]=0 {{0,0}, {3,0}} {{0,0}} wnTs[0]=1 {{0,2}, {0,0}} +debugShowLineIntersection wtTs[0]=0.333333333 {{0,3}, {0,0}} {{0,2}} wtTs[1]=1 {{0,0}} wnTs[0]=0 {{0,2}, {0,0}} wnTs[1]=1 +debugShowLineIntersection wtTs[0]=0 {{3,0}, {3,3}} {{3,0}} wnTs[0]=1 {{0,0}, {3,0}} +debugShowLineIntersection wtTs[0]=1 {{0,3}, {0,0}} {{0,0}} wnTs[0]=0 {{0,0}, {3,0}} +debugShowLineIntersection wtTs[0]=0 {{3,3}, {0,3}} {{3,3}} wnTs[0]=1 {{3,0}, {3,3}} +debugShowLineIntersection wtTs[0]=0 {{0,3}, {0,0}} {{0,3}} wnTs[0]=1 {{3,3}, {0,3}} +SkOpSegment::debugShowTs - id=0 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=1 t=1 1,0 w=1 o=0] +SkOpSegment::debugShowTs o id=4 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] +SkOpSegment::addTPair addTPair this=4 0.5 other=0 1 +SkOpSegment::debugShowTs + id=0 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=4,1 t=1 1,0 w=1 o=0] +SkOpSegment::debugShowTs o id=4 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs - id=3 [o=2 t=0 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=7 [o=10,6 t=0 0,2 w=1 o=0] [o=2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::addTPair addTPair this=3 0 other=7 0.5 +SkOpSegment::debugShowTs + id=3 [o=7,2 t=0 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=7 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs - id=0 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=4,1 t=1 1,0 w=1 o=0] +SkOpSegment::debugShowTs o id=8 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=8 0.5 other=0 1 +SkOpSegment::debugShowTs + id=0 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=8,4,1 t=1 1,0 w=1 o=0] +SkOpSegment::debugShowTs o id=8 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=3 [o=7,2 t=0 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=11 [o=10,6 t=0 0,2 w=1 o=0] [o=2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=3 0 other=11 0.5 +SkOpSegment::debugShowTs + id=3 [o=11,7,2 t=0 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=11 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=0 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=8,4,1 t=1 1,0 w=1 o=0] +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=1 t=0.333 1,0 w=1 o=0] [o=9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=12 0.333333333 other=0 1 +SkOpSegment::debugShowTs + id=0 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=12,8,4,1 t=1 1,0 w=1 o=0] +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.333 1,0 w=1 o=0] [o=9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=3 [o=11,7,2 t=0 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=10,6 t=0.333 0,2 w=1 o=0] [o=2 t=0.667 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=3 0 other=15 0.666666667 +SkOpSegment::debugShowTs + id=3 [o=15,11,7,2 t=0 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=10,6 t=0.333 0,2 w=1 o=0] [o=3,2 t=0.667 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=4 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs o id=8 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::debugShowTs + id=4 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs o id=8 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=5 [o=12,8,4 t=0 2,0 w=1 o=0] [o=10,6 t=1 2,2 w=1 o=0] +SkOpSegment::debugShowTs o id=9 [o=12,8,4 t=0 2,0 w=1 o=0] [o=10,6 t=1 2,2 w=1 o=0] operand +SkOpSegment::debugShowTs + id=5 [o=12,8,4 t=0 2,0 w=1 o=0] [o=10,6 t=1 2,2 w=1 o=0] +SkOpSegment::debugShowTs o id=9 [o=12,8,4 t=0 2,0 w=1 o=0] [o=10,6 t=1 2,2 w=1 o=0] operand +SkOpSegment::debugShowTs - id=6 [o=9,5 t=0 2,2 w=1 o=0] [o=15,11,7 t=1 0,2 w=1 o=0] +SkOpSegment::debugShowTs o id=10 [o=9,5 t=0 2,2 w=1 o=0] [o=15,11,7 t=1 0,2 w=1 o=0] operand +SkOpSegment::debugShowTs + id=6 [o=9,5 t=0 2,2 w=1 o=0] [o=15,11,7 t=1 0,2 w=1 o=0] +SkOpSegment::debugShowTs o id=10 [o=9,5 t=0 2,2 w=1 o=0] [o=15,11,7 t=1 0,2 w=1 o=0] operand +SkOpSegment::debugShowTs - id=7 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=11 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs + id=7 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=11 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=4 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.333 1,0 w=1 o=0] [o=9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=12 0.666666667 other=4 1 +SkOpSegment::debugShowTs + id=4 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=12,9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.333 1,0 w=1 o=0] [o=4,9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=7 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=10,6 t=0.333 0,2 w=1 o=0] [o=3,2 t=0.667 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=7 0 other=15 0.333333333 +SkOpSegment::debugShowTs + id=7 [o=15,10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=7,10,6 t=0.333 0,2 w=1 o=0] [o=3,2 t=0.667 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=8 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.333 1,0 w=1 o=0] [o=4,9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=12 0.666666667 other=8 1 +SkOpSegment::debugShowTs + id=8 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=12,9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.333 1,0 w=1 o=0] [o=8,4,9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::debugShowTs - id=11 [o=10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=7,10,6 t=0.333 0,2 w=1 o=0] [o=3,2 t=0.667 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=11 0 other=15 0.333333333 +SkOpSegment::debugShowTs + id=11 [o=15,10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=11,7,10,6 t=0.333 0,2 w=1 o=0] [o=3,2 t=0.667 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpContour::calcCoincidentWinding count=6 +SkOpSegment::debugShowTs p id=0 [o=15,11,7,3 t=0 0,0 w=0 o=0] [o=12,8,4,1 t=1 1,0 w=1 o=0] done +SkOpSegment::debugShowTs o id=4 [o=15,11,7,3 t=0 0,0 w=0 o=0] [o=0,1 t=0.5 1,0 w=1 o=0] [o=12,9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs p id=3 [o=15,11,7,2 t=0 0,1 w=0 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] done +SkOpSegment::debugShowTs o id=7 [o=15,10,6 t=0 0,2 w=1 o=0] [o=3,2 t=0.5 0,1 w=0 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpContour::calcCoincidentWinding count=6 +SkOpSegment::debugShowTs p id=4 [o=15,11,7,3 t=0 0,0 w=0 o=0] [o=0,1 t=0.5 1,0 w=1 o=1] [o=12,9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs o id=8 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.5 1,0 w=0 o=0] [o=12,9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::debugShowTs p id=5 [o=12,8,4 t=0 2,0 w=1 o=1] [o=10,6 t=1 2,2 w=1 o=0] +SkOpSegment::debugShowTs o id=9 [o=12,8,4 t=0 2,0 w=0 o=0] [o=10,6 t=1 2,2 w=1 o=0] operand done +SkOpSegment::debugShowTs p id=6 [o=9,5 t=0 2,2 w=1 o=1] [o=15,11,7 t=1 0,2 w=1 o=0] +SkOpSegment::debugShowTs o id=10 [o=9,5 t=0 2,2 w=0 o=0] [o=15,11,7 t=1 0,2 w=1 o=0] operand done +SkOpSegment::debugShowTs p id=7 [o=15,10,6 t=0 0,2 w=1 o=1] [o=3,2 t=0.5 0,1 w=0 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=11 [o=15,10,6 t=0 0,2 w=0 o=0] [o=3,2 t=0.5 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs p id=4 [o=15,11,7,3 t=0 0,0 w=0 o=0] [o=0,1 t=0.5 1,0 w=1 o=2] [o=12,9,5 t=1 2,0 w=1 o=0] +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=1 o=0] [o=0,1 t=0.333 1,0 w=0 o=0] [o=8,4,9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::debugShowTs p id=7 [o=15,10,6 t=0 0,2 w=1 o=2] [o=3,2 t=0.5 0,1 w=0 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=11,7,10,6 t=0.333 0,2 w=0 o=0] [o=3,2 t=0.667 0,1 w=1 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpContour::calcCoincidentWinding count=2 +SkOpSegment::debugShowTs p id=8 [o=15,11,7,3 t=0 0,0 w=2 o=0] [o=0,1 t=0.5 1,0 w=0 o=0] [o=12,9,5 t=1 2,0 w=1 o=0] operand +SkOpSegment::debugShowTs o id=12 [o=15,11,7,3 t=0 0,0 w=0 o=0] [o=0,1 t=0.333 1,0 w=0 o=0] [o=8,4,9,5 t=0.667 2,0 w=1 o=0] [o=13 t=1 3,0 w=1 o=0] operand +SkOpSegment::debugShowTs p id=11 [o=15,10,6 t=0 0,2 w=0 o=0] [o=3,2 t=0.5 0,1 w=2 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::debugShowTs o id=15 [o=14 t=0 0,3 w=1 o=0] [o=11,7,10,6 t=0.333 0,2 w=0 o=0] [o=3,2 t=0.667 0,1 w=0 o=0] [o=12,8,4,0 t=1 0,0 w=1 o=0] operand +SkOpSegment::addTPair addTPair this=12 0.333333333 other=8 0.5 +SkOpSegment::addTPair addTPair this=12 0.333333333 other=4 0.5 +SkOpSegment::addTPair addTPair duplicate this=8 0.5 other=12 0.333333333 +SkOpSegment::addTPair addTPair this=8 0.5 other=4 0.5 +SkOpSegment::addTPair addTPair duplicate this=4 0.5 other=8 0.5 +SkOpSegment::addTPair addTPair duplicate this=4 0.5 other=12 0.333333333 +SkOpSegment::addTPair addTPair this=15 0.666666667 other=11 0.5 +SkOpSegment::addTPair addTPair this=15 0.666666667 other=7 0.5 +SkOpSegment::addTPair addTPair duplicate this=11 0.5 other=15 0.666666667 +SkOpSegment::addTPair addTPair this=11 0.5 other=7 0.5 +SkOpSegment::addTPair addTPair duplicate this=7 0.5 other=11 0.5 +SkOpSegment::addTPair addTPair duplicate this=7 0.5 other=15 0.666666667 +SkOpSegment::addTPair addTPair this=4 1 other=8 1 +SkOpSegment::addTPair addTPair this=5 0 other=9 0 +SkOpSegment::addTPair addTPair duplicate this=5 0 other=9 0 +SkOpSegment::addTPair addTPair duplicate this=5 0 other=9 0 +SkOpSegment::addTPair addTPair this=6 1 other=10 1 +SkOpSegment::addTPair addTPair this=7 0 other=11 0 +SkOpSegment::addTPair addTPair duplicate this=7 0 other=11 0 +SkOpSegment::addTPair addTPair duplicate this=7 0 other=11 0 +SkOpContour::joinCoincidence count=6 +SkOpContour::joinCoincidence count=6 +SkOpContour::joinCoincidence count=2 +SkOpSegment::sortAngles [1] tStart=0 [1] +SkOpAngle::after [1/0] 23/23 tStart=0 tEnd=1 < [4/0] 31/31 tStart=0.5 tEnd=1 < [8/1] 15/15 tStart=0.5 tEnd=0 T 4 +SkOpSegment::sortAngles [2] tStart=1 [2] +SkOpAngle::after [2/0] 31/31 tStart=1 tEnd=0 < [7/1] 23/23 tStart=0.5 tEnd=0 < [11/0] 7/7 tStart=0.5 tEnd=1 F 4 +SkOpSegment::sortAngles [4] tStart=1 [9] +SkOpAngle::after [4/1] 15/15 tStart=1 tEnd=0.5 < [5/0] 23/23 tStart=0 tEnd=1 < [12/0] 31/31 tStart=0.666666667 tEnd=1 T 4 +SkOpSegment::sortAngles [5] tStart=1 [5] +SkOpSegment::sortAngles [6] tStart=1 [3] +SkOpAngle::after [6/1] 31/31 tStart=1 tEnd=0 < [7/0] 7/7 tStart=0 tEnd=0.5 < [15/0] 23/23 tStart=0.333333333 tEnd=0 T 4 +SkOpSegment::sortAngles [8] tStart=0 [1] +SkOpSegment::debugShowActiveSpans id=1 (1,0 1,1) t=0 (1,0) tEnd=1 other=12 otherT=0.333333333 otherIndex=7 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=2 (1,1 0,1) t=0 (1,1) tEnd=1 other=1 otherT=1 otherIndex=4 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=4 (0,0 2,0) t=0.5 (1,0) tEnd=1 other=8 otherT=0.5 otherIndex=4 windSum=? windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=5 (2,0 2,2) t=0 (2,0) tEnd=1 other=9 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=6 (2,2 0,2) t=0 (2,2) tEnd=1 other=9 otherT=1 otherIndex=5 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=7 (0,2 0,0) t=0 (0,2) tEnd=0.5 other=11 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=8 (0,0 2,0) t=0 (0,0) tEnd=0.5 other=15 otherT=1 otherIndex=10 windSum=? windValue=2 oppValue=0 +SkOpSegment::debugShowActiveSpans id=11 (0,2 0,0) t=0.5 (0,1) tEnd=1 other=7 otherT=0.5 otherIndex=4 windSum=? windValue=2 oppValue=0 +SkOpSegment::debugShowActiveSpans id=12 (0,0 3,0) t=0.666666667 (2,0) tEnd=1 other=8 otherT=1 otherIndex=9 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=13 (3,0 3,3) t=0 (3,0) tEnd=1 other=12 otherT=1 otherIndex=12 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=14 (3,3 0,3) t=0 (3,3) tEnd=1 other=13 otherT=1 otherIndex=1 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=15 (0,3 0,0) t=0 (0,3) tEnd=0.333333333 other=14 otherT=1 otherIndex=1 windSum=? windValue=1 oppValue=0 +SkOpSegment::findTop +SkOpAngle::debugOne [11/1] next=8/0 sect=23/23 s=1 [11] e=0.5 [7] sgn=1 windVal=2 windSum=? operand +SkOpAngle::debugOne [8/0] next=11/1 sect=31/31 s=0 [0] e=0.5 [4] sgn=-1 windVal=2 windSum=? operand stop +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [6] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [5] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [4] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [7] (0,1) tEnd=1 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [6] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [5] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [4] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 +SkOpSegment::markWinding id=11 (0,2 0,0) t=0.5 [7] (0,1) tEnd=1 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 +SkOpSegment::activeOp op=diff miFrom=0 miTo=0 suFrom=1 suTo=0 result=0 +SkOpSegment::markDoneBinary id=11 (0,2 0,0) t=0.5 [6] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 +SkOpSegment::markDoneBinary id=11 (0,2 0,0) t=0.5 [5] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 +SkOpSegment::markDoneBinary id=11 (0,2 0,0) t=0.5 [4] (0,1) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 +SkOpSegment::markDoneBinary id=11 (0,2 0,0) t=0.5 [7] (0,1) tEnd=1 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 +SkOpSegment::markWinding id=8 (0,0 2,0) t=0 [0] (0,0) tEnd=0 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 +SkOpSegment::markWinding id=8 (0,0 2,0) t=0 [1] (0,0) tEnd=0 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 +SkOpSegment::markWinding id=8 (0,0 2,0) t=0 [2] (0,0) tEnd=0 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 +SkOpSegment::markWinding id=8 (0,0 2,0) t=0 [3] (0,0) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=? windSum=? windValue=2 +SkOpSegment::markAngle last id=8 windSum=? small=0 +SkOpSegment::debugShowActiveSpans id=1 (1,0 1,1) t=0 (1,0) tEnd=1 other=12 otherT=0.333333333 otherIndex=7 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=2 (1,1 0,1) t=0 (1,1) tEnd=1 other=1 otherT=1 otherIndex=4 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=4 (0,0 2,0) t=0.5 (1,0) tEnd=1 other=8 otherT=0.5 otherIndex=4 windSum=? windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=5 (2,0 2,2) t=0 (2,0) tEnd=1 other=9 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=6 (2,2 0,2) t=0 (2,2) tEnd=1 other=9 otherT=1 otherIndex=5 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=7 (0,2 0,0) t=0 (0,2) tEnd=0.5 other=11 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=8 (0,0 2,0) t=0 (0,0) tEnd=0.5 other=15 otherT=1 otherIndex=10 windSum=-2 windValue=2 oppValue=0 +SkOpSegment::debugShowActiveSpans id=12 (0,0 3,0) t=0.666666667 (2,0) tEnd=1 other=8 otherT=1 otherIndex=9 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=13 (3,0 3,3) t=0 (3,0) tEnd=1 other=12 otherT=1 otherIndex=12 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=14 (3,3 0,3) t=0 (3,3) tEnd=1 other=13 otherT=1 otherIndex=1 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=15 (0,3 0,0) t=0 (0,3) tEnd=0.333333333 other=14 otherT=1 otherIndex=1 windSum=? windValue=1 oppValue=0 +SkOpSegment::activeOp op=diff miFrom=0 miTo=0 suFrom=1 suTo=0 result=0 +SkOpSegment::markDoneBinary id=8 (0,0 2,0) t=0 [0] (0,0) tEnd=0 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 +SkOpSegment::markDoneBinary id=8 (0,0 2,0) t=0 [1] (0,0) tEnd=0 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 +SkOpSegment::markDoneBinary id=8 (0,0 2,0) t=0 [2] (0,0) tEnd=0 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 +SkOpSegment::markDoneBinary id=8 (0,0 2,0) t=0 [3] (0,0) tEnd=0.5 newWindSum=-2 newOppSum=0 oppSum=0 windSum=-2 windValue=2 +SkOpSegment::markWinding id=1 (1,0 1,1) t=0 [0] (1,0) tEnd=0 newWindSum=1 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=1 (1,0 1,1) t=0 [1] (1,0) tEnd=0 newWindSum=1 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=1 (1,0 1,1) t=0 [2] (1,0) tEnd=0 newWindSum=1 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=1 (1,0 1,1) t=0 [3] (1,0) tEnd=1 newWindSum=1 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=2 (1,1 0,1) t=0 [0] (1,1) tEnd=1 newWindSum=1 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markAngle last id=2 windSum=? small=0 +SkOpSegment::markWinding id=4 (0,0 2,0) t=0.5 [4] (1,0) tEnd=0.5 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=4 (0,0 2,0) t=0.5 [5] (1,0) tEnd=0.5 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=4 (0,0 2,0) t=0.5 [6] (1,0) tEnd=0.5 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=4 (0,0 2,0) t=0.5 [7] (1,0) tEnd=1 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markAngle last id=4 windSum=? small=0 +SkOpSegment::debugShowActiveSpans id=1 (1,0 1,1) t=0 (1,0) tEnd=1 other=12 otherT=0.333333333 otherIndex=7 windSum=1 windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=2 (1,1 0,1) t=0 (1,1) tEnd=1 other=1 otherT=1 otherIndex=4 windSum=1 windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=4 (0,0 2,0) t=0.5 (1,0) tEnd=1 other=8 otherT=0.5 otherIndex=4 windSum=2 windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=5 (2,0 2,2) t=0 (2,0) tEnd=1 other=9 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=6 (2,2 0,2) t=0 (2,2) tEnd=1 other=9 otherT=1 otherIndex=5 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=7 (0,2 0,0) t=0 (0,2) tEnd=0.5 other=11 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=12 (0,0 3,0) t=0.666666667 (2,0) tEnd=1 other=8 otherT=1 otherIndex=9 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=13 (3,0 3,3) t=0 (3,0) tEnd=1 other=12 otherT=1 otherIndex=12 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=14 (3,3 0,3) t=0 (3,3) tEnd=1 other=13 otherT=1 otherIndex=1 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=15 (0,3 0,0) t=0 (0,3) tEnd=0.333333333 other=14 otherT=1 otherIndex=1 windSum=? windValue=1 oppValue=0 +SkOpSegment::activeOp op=diff miFrom=0 miTo=1 suFrom=1 suTo=1 result=0 +SkOpSegment::markDoneBinary id=1 (1,0 1,1) t=0 [0] (1,0) tEnd=0 newWindSum=1 newOppSum=-2 oppSum=-2 windSum=1 windValue=1 +SkOpSegment::markDoneBinary id=1 (1,0 1,1) t=0 [1] (1,0) tEnd=0 newWindSum=1 newOppSum=-2 oppSum=-2 windSum=1 windValue=1 +SkOpSegment::markDoneBinary id=1 (1,0 1,1) t=0 [2] (1,0) tEnd=0 newWindSum=1 newOppSum=-2 oppSum=-2 windSum=1 windValue=1 +SkOpSegment::markDoneBinary id=1 (1,0 1,1) t=0 [3] (1,0) tEnd=1 newWindSum=1 newOppSum=-2 oppSum=-2 windSum=1 windValue=1 +SkOpSegment::markDoneBinary id=2 (1,1 0,1) t=0 [0] (1,1) tEnd=1 newWindSum=1 newOppSum=-2 oppSum=-2 windSum=1 windValue=1 +SkOpSegment::markWinding id=7 (0,2 0,0) t=0 [0] (0,2) tEnd=0 newWindSum=-1 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=7 (0,2 0,0) t=0 [1] (0,2) tEnd=0 newWindSum=-1 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=7 (0,2 0,0) t=0 [2] (0,2) tEnd=0 newWindSum=-1 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=7 (0,2 0,0) t=0 [3] (0,2) tEnd=0.5 newWindSum=-1 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markAngle last id=7 windSum=-1 small=0 +SkOpSegment::debugShowActiveSpans id=4 (0,0 2,0) t=0.5 (1,0) tEnd=1 other=8 otherT=0.5 otherIndex=4 windSum=2 windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=5 (2,0 2,2) t=0 (2,0) tEnd=1 other=9 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=6 (2,2 0,2) t=0 (2,2) tEnd=1 other=9 otherT=1 otherIndex=5 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=7 (0,2 0,0) t=0 (0,2) tEnd=0.5 other=11 otherT=0 otherIndex=0 windSum=-1 windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=12 (0,0 3,0) t=0.666666667 (2,0) tEnd=1 other=8 otherT=1 otherIndex=9 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=13 (3,0 3,3) t=0 (3,0) tEnd=1 other=12 otherT=1 otherIndex=12 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=14 (3,3 0,3) t=0 (3,3) tEnd=1 other=13 otherT=1 otherIndex=1 windSum=? windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=15 (0,3 0,0) t=0 (0,3) tEnd=0.333333333 other=14 otherT=1 otherIndex=1 windSum=? windValue=1 oppValue=0 +SkOpSegment::activeOp op=diff miFrom=0 miTo=1 suFrom=0 suTo=1 result=0 +SkOpSegment::markDoneBinary id=7 (0,2 0,0) t=0 [0] (0,2) tEnd=0 newWindSum=-1 newOppSum=-2 oppSum=-2 windSum=-1 windValue=1 +SkOpSegment::markDoneBinary id=7 (0,2 0,0) t=0 [1] (0,2) tEnd=0 newWindSum=-1 newOppSum=-2 oppSum=-2 windSum=-1 windValue=1 +SkOpSegment::markDoneBinary id=7 (0,2 0,0) t=0 [2] (0,2) tEnd=0 newWindSum=-1 newOppSum=-2 oppSum=-2 windSum=-1 windValue=1 +SkOpSegment::markDoneBinary id=7 (0,2 0,0) t=0 [3] (0,2) tEnd=0.5 newWindSum=-1 newOppSum=-2 oppSum=-2 windSum=-1 windValue=1 +SkOpSegment::markWinding id=15 (0,3 0,0) t=0 [0] (0,3) tEnd=0.333333333 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=14 (3,3 0,3) t=0 [0] (3,3) tEnd=1 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=13 (3,0 3,3) t=0 [0] (3,0) tEnd=1 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=12 (0,0 3,0) t=0.666666667 [10] (2,0) tEnd=0.666666667 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=12 (0,0 3,0) t=0.666666667 [9] (2,0) tEnd=0.666666667 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=12 (0,0 3,0) t=0.666666667 [8] (2,0) tEnd=0.666666667 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=12 (0,0 3,0) t=0.666666667 [11] (2,0) tEnd=1 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 +SkOpSegment::markAngle last id=12 windSum=-1 small=0 +SkOpSegment::markWinding id=6 (2,2 0,2) t=0 [0] (2,2) tEnd=0 newWindSum=-1 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=6 (2,2 0,2) t=0 [1] (2,2) tEnd=1 newWindSum=-1 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markAngle last id=6 windSum=-1 small=0 +SkOpSegment::debugShowActiveSpans id=4 (0,0 2,0) t=0.5 (1,0) tEnd=1 other=8 otherT=0.5 otherIndex=4 windSum=2 windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=5 (2,0 2,2) t=0 (2,0) tEnd=1 other=9 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=6 (2,2 0,2) t=0 (2,2) tEnd=1 other=9 otherT=1 otherIndex=5 windSum=-1 windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=12 (0,0 3,0) t=0.666666667 (2,0) tEnd=1 other=8 otherT=1 otherIndex=9 windSum=-1 windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=13 (3,0 3,3) t=0 (3,0) tEnd=1 other=12 otherT=1 otherIndex=12 windSum=-1 windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=14 (3,3 0,3) t=0 (3,3) tEnd=1 other=13 otherT=1 otherIndex=1 windSum=-1 windValue=1 oppValue=0 +SkOpSegment::debugShowActiveSpans id=15 (0,3 0,0) t=0 (0,3) tEnd=0.333333333 other=14 otherT=1 otherIndex=1 windSum=-1 windValue=1 oppValue=0 +SkOpSegment::activeOp op=diff miFrom=0 miTo=0 suFrom=0 suTo=1 result=0 +SkOpSegment::markDoneBinary id=15 (0,3 0,0) t=0 [0] (0,3) tEnd=0.333333333 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 +SkOpSegment::markDoneBinary id=14 (3,3 0,3) t=0 [0] (3,3) tEnd=1 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 +SkOpSegment::markDoneBinary id=13 (3,0 3,3) t=0 [0] (3,0) tEnd=1 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 +SkOpSegment::markDoneBinary id=12 (0,0 3,0) t=0.666666667 [10] (2,0) tEnd=0.666666667 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 +SkOpSegment::markDoneBinary id=12 (0,0 3,0) t=0.666666667 [9] (2,0) tEnd=0.666666667 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 +SkOpSegment::markDoneBinary id=12 (0,0 3,0) t=0.666666667 [8] (2,0) tEnd=0.666666667 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 +SkOpSegment::markDoneBinary id=12 (0,0 3,0) t=0.666666667 [11] (2,0) tEnd=1 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 +SkOpSegment::debugShowActiveSpans id=4 (0,0 2,0) t=0.5 (1,0) tEnd=1 other=8 otherT=0.5 otherIndex=4 windSum=2 windValue=1 oppValue=2 +SkOpSegment::debugShowActiveSpans id=5 (2,0 2,2) t=0 (2,0) tEnd=1 other=9 otherT=0 otherIndex=0 windSum=? windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=6 (2,2 0,2) t=0 (2,2) tEnd=1 other=9 otherT=1 otherIndex=5 windSum=-1 windValue=1 oppValue=1 +SkOpSegment::activeOp op=diff miFrom=0 miTo=1 suFrom=0 suTo=1 result=0 +SkOpSegment::markDoneBinary id=4 (0,0 2,0) t=0.5 [6] (1,0) tEnd=0.5 newWindSum=2 newOppSum=-2 oppSum=-2 windSum=2 windValue=1 +SkOpSegment::markDoneBinary id=4 (0,0 2,0) t=0.5 [5] (1,0) tEnd=0.5 newWindSum=2 newOppSum=-2 oppSum=-2 windSum=2 windValue=1 +SkOpSegment::markDoneBinary id=4 (0,0 2,0) t=0.5 [4] (1,0) tEnd=0.5 newWindSum=2 newOppSum=-2 oppSum=-2 windSum=2 windValue=1 +SkOpSegment::markDoneBinary id=4 (0,0 2,0) t=0.5 [7] (1,0) tEnd=1 newWindSum=2 newOppSum=-2 oppSum=-2 windSum=2 windValue=1 +SkOpSegment::markWinding id=5 (2,0 2,2) t=0 [0] (2,0) tEnd=0 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=5 (2,0 2,2) t=0 [1] (2,0) tEnd=0 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=5 (2,0 2,2) t=0 [2] (2,0) tEnd=0 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markWinding id=5 (2,0 2,2) t=0 [3] (2,0) tEnd=1 newWindSum=2 newOppSum=-2 oppSum=? windSum=? windValue=1 +SkOpSegment::markAngle last id=5 windSum=? small=0 +SkOpSegment::debugShowActiveSpans id=5 (2,0 2,2) t=0 (2,0) tEnd=1 other=9 otherT=0 otherIndex=0 windSum=2 windValue=1 oppValue=1 +SkOpSegment::debugShowActiveSpans id=6 (2,2 0,2) t=0 (2,2) tEnd=1 other=9 otherT=1 otherIndex=5 windSum=-1 windValue=1 oppValue=1 +SkOpSegment::activeOp op=diff miFrom=1 miTo=0 suFrom=1 suTo=1 result=0 +SkOpSegment::markDoneBinary id=5 (2,0 2,2) t=0 [0] (2,0) tEnd=0 newWindSum=2 newOppSum=-2 oppSum=-2 windSum=2 windValue=1 +SkOpSegment::markDoneBinary id=5 (2,0 2,2) t=0 [1] (2,0) tEnd=0 newWindSum=2 newOppSum=-2 oppSum=-2 windSum=2 windValue=1 +SkOpSegment::markDoneBinary id=5 (2,0 2,2) t=0 [2] (2,0) tEnd=0 newWindSum=2 newOppSum=-2 oppSum=-2 windSum=2 windValue=1 +SkOpSegment::markDoneBinary id=5 (2,0 2,2) t=0 [3] (2,0) tEnd=1 newWindSum=2 newOppSum=-2 oppSum=-2 windSum=2 windValue=1 +SkOpSegment::debugShowActiveSpans id=6 (2,2 0,2) t=0 (2,2) tEnd=1 other=9 otherT=1 otherIndex=5 windSum=-1 windValue=1 oppValue=1 +SkOpSegment::activeOp op=diff miFrom=1 miTo=0 suFrom=1 suTo=1 result=0 +SkOpSegment::markDoneBinary id=6 (2,2 0,2) t=0 [0] (2,2) tEnd=0 newWindSum=-1 newOppSum=-2 oppSum=-2 windSum=-1 windValue=1 +SkOpSegment::markDoneBinary id=6 (2,2 0,2) t=0 [1] (2,2) tEnd=1 newWindSum=-1 newOppSum=-2 oppSum=-2 windSum=-1 windValue=1 +</div> + +</div> + +<script type="text/javascript"> + +var testDivs = [ + rects4, + refRects4, +]; + +var decimal_places = 3; // make this 3 to show more precision + +var tests = []; +var testLines = []; +var testTitles = []; +var testIndex = 0; +var ctx; + +var xmin, xmax, focusXmin, focusXmax; +var ymin, ymax, focusYmin, focusYmax; +var scale; +var mouseX, mouseY; +var srcLeft, srcTop; +var screenWidth, screenHeight; +var drawnPts, drawnLines, drawnQuads, drawnCubics; +var curveT = 0; + +var pt_labels = 2; +var collect_bounds = false; +var control_lines = 0; +var curve_t = false; +var debug_xy = 1; +var focus_enabled = false; +var focus_on_selection = false; +var step_limit = 0; +var draw_active = false; +var draw_add = false; +var draw_angle = 0; +var draw_deriviatives = 0; +var draw_hints = false; +var draw_hodo = 0; +var draw_id = false; +var draw_intersection = 0; +var draw_intersectT = false; +var draw_legend = true; +var draw_log = false; +var draw_mark = false; +var draw_midpoint = false; +var draw_op = 0; +var draw_sequence = false; +var draw_sort = 0; +var draw_path = 3; +var draw_computed = 0; +var retina_scale = !!window.devicePixelRatio; + +var activeCount = 0; +var addCount = 0; +var angleCount = 0; +var opCount = 0; +var sectCount = 0; +var sortCount = 0; +var markCount = 0; +var activeMax = 0; +var addMax = 0; +var angleMax = 0; +var sectMax = 0; +var sectMax2 = 0; +var sortMax = 0; +var markMax = 0; +var opMax = 0; +var stepMax = 0; +var lastIndex = 0; +var hasPath = false; +var hasComputedPath = false; + +var firstActiveSpan = -1; +var logStart = -1; +var logRange = 0; + +var SPAN_ID = 0; +var SPAN_X1 = SPAN_ID + 1; +var SPAN_Y1 = SPAN_X1 + 1; +var SPAN_X2 = SPAN_Y1 + 1; +var SPAN_Y2 = SPAN_X2 + 1; +var SPAN_L_T = SPAN_Y2 + 1; +var SPAN_L_TX = SPAN_L_T + 1; +var SPAN_L_TY = SPAN_L_TX + 1; +var SPAN_L_TEND = SPAN_L_TY + 1; +var SPAN_L_OTHER = SPAN_L_TEND + 1; +var SPAN_L_OTHERT = SPAN_L_OTHER + 1; +var SPAN_L_OTHERI = SPAN_L_OTHERT + 1; +var SPAN_L_SUM = SPAN_L_OTHERI + 1; +var SPAN_L_VAL = SPAN_L_SUM + 1; +var SPAN_L_OPP = SPAN_L_VAL + 1; + +var SPAN_X3 = SPAN_Y2 + 1; +var SPAN_Y3 = SPAN_X3 + 1; +var SPAN_Q_T = SPAN_Y3 + 1; +var SPAN_Q_TX = SPAN_Q_T + 1; +var SPAN_Q_TY = SPAN_Q_TX + 1; +var SPAN_Q_TEND = SPAN_Q_TY + 1; +var SPAN_Q_OTHER = SPAN_Q_TEND + 1; +var SPAN_Q_OTHERT = SPAN_Q_OTHER + 1; +var SPAN_Q_OTHERI = SPAN_Q_OTHERT + 1; +var SPAN_Q_SUM = SPAN_Q_OTHERI + 1; +var SPAN_Q_VAL = SPAN_Q_SUM + 1; +var SPAN_Q_OPP = SPAN_Q_VAL + 1; + +var SPAN_X4 = SPAN_Y3 + 1; +var SPAN_Y4 = SPAN_X4 + 1; +var SPAN_C_T = SPAN_Y4 + 1; +var SPAN_C_TX = SPAN_C_T + 1; +var SPAN_C_TY = SPAN_C_TX + 1; +var SPAN_C_TEND = SPAN_C_TY + 1; +var SPAN_C_OTHER = SPAN_C_TEND + 1; +var SPAN_C_OTHERT = SPAN_C_OTHER + 1; +var SPAN_C_OTHERI = SPAN_C_OTHERT + 1; +var SPAN_C_SUM = SPAN_C_OTHERI + 1; +var SPAN_C_VAL = SPAN_C_SUM + 1; +var SPAN_C_OPP = SPAN_C_VAL + 1; + +var ACTIVE_LINE_SPAN = 1; +var ACTIVE_QUAD_SPAN = ACTIVE_LINE_SPAN + 1; +var ACTIVE_CUBIC_SPAN = ACTIVE_QUAD_SPAN + 1; + +var ADD_MOVETO = ACTIVE_CUBIC_SPAN + 1; +var ADD_LINETO = ADD_MOVETO + 1; +var ADD_QUADTO = ADD_LINETO + 1; +var ADD_CUBICTO = ADD_QUADTO + 1; +var ADD_CLOSE = ADD_CUBICTO + 1; +var ADD_FILL = ADD_CLOSE + 1; + +var PATH_LINE = ADD_FILL + 1; +var PATH_QUAD = PATH_LINE + 1; +var PATH_CUBIC = PATH_QUAD + 1; + +var INTERSECT_LINE = PATH_CUBIC + 1; +var INTERSECT_LINE_2 = INTERSECT_LINE + 1; +var INTERSECT_LINE_NO = INTERSECT_LINE_2 + 1; +var INTERSECT_QUAD_LINE = INTERSECT_LINE_NO + 1; +var INTERSECT_QUAD_LINE_2 = INTERSECT_QUAD_LINE + 1; +var INTERSECT_QUAD_LINE_NO = INTERSECT_QUAD_LINE_2 + 1; +var INTERSECT_QUAD = INTERSECT_QUAD_LINE_NO + 1; +var INTERSECT_QUAD_2 = INTERSECT_QUAD + 1; +var INTERSECT_QUAD_NO = INTERSECT_QUAD_2 + 1; +var INTERSECT_SELF_CUBIC = INTERSECT_QUAD_NO + 1; +var INTERSECT_SELF_CUBIC_NO = INTERSECT_SELF_CUBIC + 1; +var INTERSECT_CUBIC_LINE = INTERSECT_SELF_CUBIC_NO + 1; +var INTERSECT_CUBIC_LINE_2 = INTERSECT_CUBIC_LINE + 1; +var INTERSECT_CUBIC_LINE_3 = INTERSECT_CUBIC_LINE_2 + 1; +var INTERSECT_CUBIC_LINE_NO = INTERSECT_CUBIC_LINE_3 + 1; +var INTERSECT_CUBIC_QUAD = INTERSECT_CUBIC_LINE_NO + 1; +var INTERSECT_CUBIC_QUAD_2 = INTERSECT_CUBIC_QUAD + 1; +var INTERSECT_CUBIC_QUAD_3 = INTERSECT_CUBIC_QUAD_2 + 1; +var INTERSECT_CUBIC_QUAD_4 = INTERSECT_CUBIC_QUAD_3 + 1; +var INTERSECT_CUBIC_QUAD_NO = INTERSECT_CUBIC_QUAD_4 + 1; +var INTERSECT_CUBIC = INTERSECT_CUBIC_QUAD_NO + 1; +var INTERSECT_CUBIC_2 = INTERSECT_CUBIC + 1; +var INTERSECT_CUBIC_3 = INTERSECT_CUBIC_2 + 1; +var INTERSECT_CUBIC_4 = INTERSECT_CUBIC_3 + 1; +// FIXME: add cubic 5- 9 +var INTERSECT_CUBIC_NO = INTERSECT_CUBIC_4 + 1; + +var SORT_UNARY = INTERSECT_CUBIC_NO + 1; +var SORT_BINARY = SORT_UNARY + 1; + +var OP_DIFFERENCE = SORT_BINARY + 1; +var OP_INTERSECT = OP_DIFFERENCE + 1; +var OP_UNION = OP_INTERSECT + 1; +var OP_XOR = OP_UNION + 1; + +var MARK_LINE = OP_XOR + 1; +var MARK_QUAD = MARK_LINE + 1; +var MARK_CUBIC = MARK_QUAD + 1; +var MARK_DONE_LINE = MARK_CUBIC + 1; +var MARK_DONE_QUAD = MARK_DONE_LINE + 1; +var MARK_DONE_CUBIC = MARK_DONE_QUAD + 1; +var MARK_UNSORTABLE_LINE = MARK_DONE_CUBIC + 1; +var MARK_UNSORTABLE_QUAD = MARK_UNSORTABLE_LINE + 1; +var MARK_UNSORTABLE_CUBIC = MARK_UNSORTABLE_QUAD + 1; +var MARK_SIMPLE_LINE = MARK_UNSORTABLE_CUBIC + 1; +var MARK_SIMPLE_QUAD = MARK_SIMPLE_LINE + 1; +var MARK_SIMPLE_CUBIC = MARK_SIMPLE_QUAD + 1; +var MARK_SIMPLE_DONE_LINE = MARK_SIMPLE_CUBIC + 1; +var MARK_SIMPLE_DONE_QUAD = MARK_SIMPLE_DONE_LINE + 1; +var MARK_SIMPLE_DONE_CUBIC = MARK_SIMPLE_DONE_QUAD + 1; +var MARK_DONE_UNARY_LINE = MARK_SIMPLE_DONE_CUBIC + 1; +var MARK_DONE_UNARY_QUAD = MARK_DONE_UNARY_LINE + 1; +var MARK_DONE_UNARY_CUBIC = MARK_DONE_UNARY_QUAD + 1; +var MARK_ANGLE_LAST = MARK_DONE_UNARY_CUBIC + 1; + +var COMPUTED_SET_1 = MARK_ANGLE_LAST + 1; +var COMPUTED_SET_2 = COMPUTED_SET_1 + 1; + +var ANGLE_AFTER = COMPUTED_SET_2; +var ANGLE_AFTER2 = ANGLE_AFTER + 1; + +var ACTIVE_OP = ANGLE_AFTER2 + 1; + +var FRAG_TYPE_LAST = ACTIVE_OP; + +var REC_TYPE_UNKNOWN = -1; +var REC_TYPE_PATH = 0; +var REC_TYPE_SECT = 1; +var REC_TYPE_ACTIVE = 2; +var REC_TYPE_ADD = 3; +var REC_TYPE_SORT = 4; +var REC_TYPE_OP = 5; +var REC_TYPE_MARK = 6; +var REC_TYPE_COMPUTED = 7; +var REC_TYPE_COIN = 8; +var REC_TYPE_ANGLE = 9; +var REC_TYPE_ACTIVE_OP = 10; +var REC_TYPE_LAST = REC_TYPE_ACTIVE_OP; + +function strs_to_nums(strs) { + var result = []; + for (var idx = 1; idx < strs.length; ++idx) { + var str = strs[idx]; + var num = parseFloat(str); + if (isNaN(num)) { + result.push(str); + } else { + result.push(num); + } + } + return result; +} + +function filter_str_by(id, str, regex, array) { + if (regex.test(str)) { + var strs = regex.exec(str); + var result = strs_to_nums(strs); + array.push(id); + array.push(result); + return true; + } + return false; +} + +function construct_regexp2(pattern) { + var escape = pattern.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); + escape = escape.replace(/UNSORTABLE/g, "\\*\\*\\* UNSORTABLE \\*\\*\\*"); + escape = escape.replace(/CUBIC_VAL/g, "\\(P_VAL P_VAL P_VAL P_VAL\\)"); + escape = escape.replace(/QUAD_VAL/g, "\\(P_VAL P_VAL P_VAL\\)"); + escape = escape.replace(/LINE_VAL/g, "\\(P_VAL P_VAL\\)"); + escape = escape.replace(/FILL_TYPE/g, "SkPath::k[a-zA-Z]+_FillType"); + escape = escape.replace(/PT_VAL/g, "\\(P_VAL\\)"); + escape = escape.replace(/P_VAL/g, "(-?\\d+\\.?\\d*(?:e-?\\d+)?)[Ff]?, ?(-?\\d+\\.?\\d*(?:e-?\\d+)?)[Ff]?"); + escape = escape.replace(/T_VAL/g, "(-?\\d+\\.?\\d*(?:e-?\\d+)?)"); + escape = escape.replace(/PATH/g, "pathB?"); + escape = escape.replace(/IDX/g, "(\\d+)"); + escape = escape.replace(/NUM/g, "(-?\\d+)"); + escape = escape.replace(/OPT/g, "(\\?|-?\\d+)"); + return new RegExp(escape, 'i'); +} + +function construct_regexp2c(pattern) { + var escape = pattern.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); + escape = escape.replace(/UNSORTABLE/g, "\\*\\*\\* UNSORTABLE \\*\\*\\*"); + escape = escape.replace(/CUBIC_VAL/g, "(?:\\$\\d = )?\\{\\{P_VAL\\}, \\{P_VAL\\}, \\{P_VAL\\}, \\{P_VAL\\}\\}"); + escape = escape.replace(/QUAD_VAL/g, "(?:\\$\\d = )?\\{\\{P_VAL\\}, \\{P_VAL\\}, \\{P_VAL\\}\\}"); + escape = escape.replace(/LINE_VAL/g, "(?:\\$\\d = )?\\{\\{P_VAL\\}, \\{P_VAL\\}\\}"); + escape = escape.replace(/FILL_TYPE/g, "SkPath::k[a-zA-Z]+_FillType"); + escape = escape.replace(/PT_VAL/g, "\\{\\{P_VAL\\}\\}"); + escape = escape.replace(/P_VAL/g, "(?:f?[xX] = )?(-?\\d+\\.?\\d*(?:e-?\\d+)?)[Ff]?,(?: f?[yY] = )?(-?\\d+\\.?\\d*(?:e-?\\d+)?)[Ff]?"); + escape = escape.replace(/T_VAL/g, "(-?\\d+\\.?\\d*(?:e-?\\d+)?)"); + escape = escape.replace(/OPER/g, "[a-z]+"); + escape = escape.replace(/PATH/g, "pathB?"); + escape = escape.replace(/T_F/g, "([TF])"); + escape = escape.replace(/IDX/g, "(\\d+)"); + escape = escape.replace(/NUM/g, "(-?\\d+)"); + escape = escape.replace(/OPT/g, "(\\?|-?\\d+)"); + return new RegExp(escape, 'i'); +} + +function match_regexp(str, lineNo, array, id, pattern) { + var regex = construct_regexp2(pattern); + if (filter_str_by(id, str, regex, array)) { + return true; + } + regex = construct_regexp2c(pattern); + return filter_str_by(id, str, regex, array); +} + +function endsWith(str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; +} + +function parse_all(test) { + var lines = test.match(/[^\r\n]+/g); + var records = []; // a rec can be the original paths, a set of intersections, a set of active spans, a sort, or a path add + var record = []; + var recType = REC_TYPE_UNKNOWN; + var lastLineNo; + var moveX, moveY; + for (var lineNo = 0; lineNo < lines.length; ++lineNo) { + var line = lines[lineNo]; + if (line.length == 0) { + continue; + } + var opStart = "SkOpSegment::"; + if (line.lastIndexOf(opStart, 0) === 0) { + line = line.substr(opStart.length); + } + var angleStart = "SkOpAngle::"; + if (line.lastIndexOf(angleStart, 0) === 0) { + line = line.substr(angleStart.length); + } + var type = line.lastIndexOf("debugShowActiveSpans", 0) === 0 ? REC_TYPE_ACTIVE + : line.lastIndexOf("debugShowTs", 0) === 0 ? REC_TYPE_COIN + : line.lastIndexOf("debugShow", 0) === 0 ? REC_TYPE_SECT + : line.lastIndexOf("activeOp", 0) === 0 ? REC_TYPE_ACTIVE_OP + : line.lastIndexOf("computed", 0) === 0 ? REC_TYPE_COMPUTED + : line.lastIndexOf("debugOne", 0) === 0 ? REC_TYPE_SORT + : line.lastIndexOf("dumpOne", 0) === 0 ? REC_TYPE_SORT + : line.lastIndexOf("pathB.", 0) === 0 ? REC_TYPE_ADD + : line.lastIndexOf("path.", 0) === 0 ? REC_TYPE_ADD + : line.lastIndexOf("after", 0) === 0 ? REC_TYPE_ANGLE + : line.lastIndexOf("mark", 0) === 0 ? REC_TYPE_MARK + : line.lastIndexOf(" {{", 0) === 0 ? REC_TYPE_COMPUTED + : line.lastIndexOf("{{", 0) === 0 ? REC_TYPE_PATH + : line.lastIndexOf("op", 0) === 0 ? REC_TYPE_OP + : line.lastIndexOf("$", 0) === 0 ? REC_TYPE_PATH + : REC_TYPE_UNKNOWN; + if (recType != type || recType == REC_TYPE_ADD || recType == REC_TYPE_SECT + || recType == REC_TYPE_ACTIVE_OP || recType == REC_TYPE_ANGLE) { + if (recType != REC_TYPE_UNKNOWN) { + records.push(recType); + records.push(lastLineNo); + records.push(record); + } + record = []; + recType = type; + lastLineNo = lineNo; + } + var found = false; + switch (recType) { + case REC_TYPE_ACTIVE: + found = match_regexp(line, lineNo, record, ACTIVE_LINE_SPAN, "debugShowActiveSpans" + +" id=IDX LINE_VAL t=T_VAL PT_VAL tEnd=T_VAL other=IDX otherT=T_VAL otherIndex=IDX windSum=OPT windValue=IDX oppValue=NUM" + ) || match_regexp(line, lineNo, record, ACTIVE_QUAD_SPAN, "debugShowActiveSpans" + +" id=IDX QUAD_VAL t=T_VAL PT_VAL tEnd=T_VAL other=IDX otherT=T_VAL otherIndex=IDX windSum=OPT windValue=IDX oppValue=NUM" + ) || match_regexp(line, lineNo, record, ACTIVE_CUBIC_SPAN, "debugShowActiveSpans" + +" id=IDX CUBIC_VAL t=T_VAL PT_VAL tEnd=T_VAL other=IDX otherT=T_VAL otherIndex=IDX windSum=OPT windValue=IDX oppValue=NUM" + ); + break; + case REC_TYPE_ACTIVE_OP: + found = match_regexp(line, lineNo, record, ACTIVE_OP, "activeOp" + +" id=IDX t=T_VAL tEnd=T_VAL op=OPER miFrom=NUM miTo=NUM suFrom=NUM suTo=NUM result=IDX" + ); + break; + case REC_TYPE_ADD: + if (match_regexp(line, lineNo, record, ADD_MOVETO, "PATH.moveTo(P_VAL);")) { + moveX = record[1][0]; + moveY = record[1][1]; + found = true; + } else if (match_regexp(line, lineNo, record, ADD_LINETO, "PATH.lineTo(P_VAL);")) { + record[1].unshift(moveY); + record[1].unshift(moveX); + moveX = record[1][2]; + moveY = record[1][3]; + found = true; + } else if (match_regexp(line, lineNo, record, ADD_QUADTO, "PATH.quadTo(P_VAL, P_VAL);")) { + record[1].unshift(moveY); + record[1].unshift(moveX); + moveX = record[1][4]; + moveY = record[1][5]; + found = true; + } else if (match_regexp(line, lineNo, record, ADD_CUBICTO, "PATH.cubicTo(P_VAL, P_VAL, P_VAL);")) { + record[1].unshift(moveY); + record[1].unshift(moveX); + moveX = record[1][6]; + moveY = record[1][7]; + found = true; + } else if (match_regexp(line, lineNo, record, ADD_FILL, "PATH.setFillType(FILL_TYPE);")) { + found = true; + } else { + found = match_regexp(line, lineNo, record, ADD_CLOSE, "PATH.close();"); + } + break; + case REC_TYPE_ANGLE: + found = match_regexp(line, lineNo, record, ANGLE_AFTER, "after " + +"id=IDX IDX/IDX tStart=T_VAL tEnd=T_VAL < id=IDX IDX/IDX tStart=T_VAL tEnd=T_VAL < id=IDX IDX/IDX tStart=T_VAL tEnd=T_VAL T_F IDX"); + if (found) { + break; + } + found = match_regexp(line, lineNo, record, ANGLE_AFTER2, "after " + +"[IDX/IDX] NUM/NUM tStart=T_VAL tEnd=T_VAL < [IDX/IDX] NUM/NUM tStart=T_VAL tEnd=T_VAL < [IDX/IDX] NUM/NUM tStart=T_VAL tEnd=T_VAL T_F IDX"); + break; + case REC_TYPE_COIN: + found = true; + break; + case REC_TYPE_COMPUTED: + found = line == "computed quadratics given" + || match_regexp(line, lineNo, record, COMPUTED_SET_1, "computed quadratics set 1" + ) || match_regexp(line, lineNo, record, COMPUTED_SET_2, "computed quadratics set 2" + ) || match_regexp(line, lineNo, record, PATH_QUAD, " QUAD_VAL," + ) || match_regexp(line, lineNo, record, PATH_CUBIC, " CUBIC_VAL," + ); + break; + case REC_TYPE_PATH: + found = match_regexp(line, lineNo, record, PATH_LINE, "LINE_VAL" + ) || match_regexp(line, lineNo, record, PATH_QUAD, "QUAD_VAL" + ) || match_regexp(line, lineNo, record, PATH_CUBIC, "CUBIC_VAL" + ); + break; + case REC_TYPE_SECT: + found = match_regexp(line, lineNo, record, INTERSECT_LINE, "debugShowLineIntersection" + +" wtTs[0]=T_VAL LINE_VAL PT_VAL wnTs[0]=T_VAL LINE_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_LINE_2, "debugShowLineIntersection" + +" wtTs[0]=T_VAL LINE_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wnTs[0]=T_VAL LINE_VAL wnTs[1]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_LINE_NO, "debugShowLineIntersection" + +" no intersect LINE_VAL LINE_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_QUAD_LINE, "debugShowQuadLineIntersection" + +" wtTs[0]=T_VAL QUAD_VAL PT_VAL wnTs[0]=T_VAL LINE_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_QUAD_LINE_2, "debugShowQuadLineIntersection" + +" wtTs[0]=T_VAL QUAD_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wnTs[0]=T_VAL LINE_VAL wnTs[1]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_QUAD_LINE_NO, "debugShowQuadLineIntersection" + +" no intersect QUAD_VAL LINE_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_QUAD, "debugShowQuadIntersection" + +" wtTs[0]=T_VAL QUAD_VAL PT_VAL wnTs[0]=T_VAL QUAD_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_QUAD_2, "debugShowQuadIntersection" + +" wtTs[0]=T_VAL QUAD_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wnTs[0]=T_VAL QUAD_VAL wnTs[1]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_QUAD_NO, "debugShowQuadIntersection" + +" no intersect QUAD_VAL QUAD_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_LINE, "debugShowCubicLineIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wnTs[0]=T_VAL LINE_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_LINE_2, "debugShowCubicLineIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wnTs[0]=T_VAL LINE_VAL wnTs[1]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_LINE_3, "debugShowCubicLineIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wtTs[2]=T_VAL PT_VAL wnTs[0]=T_VAL LINE_VAL wnTs[1]=T_VAL wnTs[2]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_LINE_NO, "debugShowCubicLineIntersection" + +" no intersect CUBIC_VAL LINE_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_QUAD, "debugShowCubicQuadIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wnTs[0]=T_VAL QUAD_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_QUAD_2, "debugShowCubicQuadIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wnTs[0]=T_VAL QUAD_VAL wnTs[1]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_QUAD_3, "debugShowCubicQuadIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wtTs[2]=T_VAL PT_VAL wnTs[0]=T_VAL QUAD_VAL wnTs[1]=T_VAL wnTs[2]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_QUAD_4, "debugShowCubicQuadIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wtTs[2]=T_VAL wtTs[3]=T_VAL PT_VAL wnTs[0]=T_VAL QUAD_VAL wnTs[1]=T_VAL wnTs[2]=T_VAL wnTs[3]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_QUAD_NO, "debugShowCubicQuadIntersection" + +" no intersect CUBIC_VAL QUAD_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC, "debugShowCubicIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wnTs[0]=T_VAL CUBIC_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_2, "debugShowCubicIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wnTs[0]=T_VAL CUBIC_VAL wnTs[1]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_3, "debugShowCubicIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wtTs[2]=T_VAL PT_VAL wnTs[0]=T_VAL CUBIC_VAL wnTs[1]=T_VAL wnTs[2]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_4, "debugShowCubicIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL PT_VAL wtTs[2]=T_VAL PT_VAL wtTs[3]=T_VAL PT_VAL wnTs[0]=T_VAL CUBIC_VAL wnTs[1]=T_VAL wnTs[2]=T_VAL wnTs[3]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_CUBIC_NO, "debugShowCubicIntersection" + +" no intersect CUBIC_VAL CUBIC_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_SELF_CUBIC, "debugShowCubicIntersection" + +" wtTs[0]=T_VAL CUBIC_VAL PT_VAL wtTs[1]=T_VAL" + ) || match_regexp(line, lineNo, record, INTERSECT_SELF_CUBIC_NO, "debugShowCubicIntersection" + +" no self intersect CUBIC_VAL" + ); + break; + case REC_TYPE_SORT: + var hasDone = / done/.test(line); + var hasUnorderable = / unorderable/.test(line); + var hasSmall = / small/.test(line); + var hasTiny = / tiny/.test(line); + var hasOperand = / operand/.test(line); + var hasStop = / stop/.test(line); + line.replace(/[ a-z]+$/, ""); + found = match_regexp(line, lineNo, record, SORT_UNARY, "debugOne" + +" [IDX/IDX] next=IDX/IDX sect=IDX/IDX s=T_VAL [IDX] e=T_VAL [IDX] sgn=NUM windVal=IDX windSum=OPT" + ) || match_regexp(line, lineNo, record, SORT_BINARY, "debugOne" + +" [IDX/IDX] next=IDX/IDX sect=IDX/IDX s=T_VAL [IDX] e=T_VAL [IDX] sgn=NUM windVal=IDX windSum=OPT oppVal=IDX oppSum=OPT" + ) || match_regexp(line, lineNo, record, SORT_UNARY, "dumpOne" + +" [IDX/IDX] next=IDX/IDX sect=NUM/NUM s=T_VAL [IDX] e=T_VAL [IDX] sgn=NUM windVal=IDX windSum=OPT" + ) || match_regexp(line, lineNo, record, SORT_BINARY, "dumpOne" + +" [IDX/IDX] next=IDX/IDX sect=NUM/NUM s=T_VAL [IDX] e=T_VAL [IDX] sgn=NUM windVal=IDX windSum=OPT oppVal=IDX oppSum=OPT" + ); + if (found) { + record[1].push(hasDone); + record[1].push(hasUnorderable); + record[1].push(hasSmall); + record[1].push(hasTiny); + record[1].push(hasOperand); + record[1].push(hasStop); + } + break; + case REC_TYPE_MARK: + found = match_regexp(line, lineNo, record, MARK_LINE, "markWinding" + +" id=IDX LINE_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM newOppSum=NUM oppSum=OPT windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_QUAD, "markWinding" + +" id=IDX QUAD_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM newOppSum=NUM oppSum=OPT windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_CUBIC, "markWinding" + +" id=IDX CUBIC_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM newOppSum=NUM oppSum=OPT windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_DONE_LINE, "markDoneBinary" + +" id=IDX LINE_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM newOppSum=NUM oppSum=OPT windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_DONE_QUAD, "markDoneBinary" + +" id=IDX QUAD_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM newOppSum=NUM oppSum=OPT windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_DONE_CUBIC, "markDoneBinary" + +" id=IDX CUBIC_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM newOppSum=NUM oppSum=OPT windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_UNSORTABLE_LINE, "markUnsortable" + +" id=IDX LINE_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_UNSORTABLE_QUAD, "markUnsortable" + +" id=IDX QUAD_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_UNSORTABLE_CUBIC, "markUnsortable" + +" id=IDX CUBIC_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_SIMPLE_LINE, "markWinding" + +" id=IDX LINE_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_SIMPLE_QUAD, "markWinding" + +" id=IDX QUAD_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_SIMPLE_CUBIC, "markWinding" + +" id=IDX CUBIC_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_SIMPLE_DONE_LINE, "markDone" + +" id=IDX LINE_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_SIMPLE_DONE_QUAD, "markDone" + +" id=IDX QUAD_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_SIMPLE_DONE_CUBIC, "markDone" + +" id=IDX CUBIC_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_DONE_UNARY_LINE, "markDoneUnary" + +" id=IDX LINE_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_DONE_UNARY_QUAD, "markDoneUnary" + +" id=IDX QUAD_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_DONE_UNARY_CUBIC, "markDoneUnary" + +" id=IDX CUBIC_VAL t=T_VAL [IDX] PT_VAL tEnd=T_VAL newWindSum=NUM windSum=OPT windValue=IDX" + ) || match_regexp(line, lineNo, record, MARK_ANGLE_LAST, "markAngle" + +" last id=IDX windSum=OPT small=IDX"); + break; + case REC_TYPE_OP: + if (line.lastIndexOf("oppSign oppSign=", 0) === 0 + || line.lastIndexOf("operator<", 0) === 0) { + found = true; + break; + } + found = match_regexp(line, lineNo, record, OP_DIFFERENCE, "op difference" + ) || match_regexp(line, lineNo, record, OP_INTERSECT, "op intersect" + ) || match_regexp(line, lineNo, record, OP_UNION, "op union" + ) || match_regexp(line, lineNo, record, OP_XOR, "op xor" + ); + break; + case REC_TYPE_UNKNOWN: + found = true; + break; + } + if (!found) { + console.log(line + " [" + lineNo + "] of type " + type + " not found"); + } + } + if (recType != REC_TYPE_UNKNOWN) { + records.push(recType); + records.push(lastLineNo); + records.push(record); + } + if (records.length >= 1) { + tests[testIndex] = records; + testLines[testIndex] = lines; + } +} + +function init(test) { + var canvas = document.getElementById('canvas'); + if (!canvas.getContext) return; + ctx = canvas.getContext('2d'); + var resScale = retina_scale && window.devicePixelRatio ? window.devicePixelRatio : 1; + var unscaledWidth = window.innerWidth - 20; + var unscaledHeight = window.innerHeight - 20; + screenWidth = unscaledWidth; + screenHeight = unscaledHeight; + canvas.width = unscaledWidth * resScale; + canvas.height = unscaledHeight * resScale; + canvas.style.width = unscaledWidth + 'px'; + canvas.style.height = unscaledHeight + 'px'; + if (resScale != 1) { + ctx.scale(resScale, resScale); + } + xmin = Infinity; + xmax = -Infinity; + ymin = Infinity; + ymax = -Infinity; + hasPath = hasComputedPath = false; + firstActiveSpan = -1; + for (var tIndex = 0; tIndex < test.length; tIndex += 3) { + var recType = test[tIndex]; + if (!typeof recType == 'number' || recType < REC_TYPE_UNKNOWN || recType > REC_TYPE_LAST) { + console.log("unknown rec type: " + recType); + throw "stop execution"; + } + var records = test[tIndex + 2]; + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + if (!typeof fragType == 'number' || fragType < 1 || fragType > FRAG_TYPE_LAST) { + console.log("unknown in range frag type: " + fragType); + throw "stop execution"; + } + var frags = records[recordIndex + 1]; + var first = 0; + var last = -1; + var first2 = 0; + var last2 = 0; + switch (recType) { + case REC_TYPE_COMPUTED: + if (fragType == COMPUTED_SET_1 || fragType == COMPUTED_SET_2) { + break; + } + hasComputedPath = true; + case REC_TYPE_PATH: + switch (fragType) { + case PATH_LINE: + last = 4; + break; + case PATH_QUAD: + last = 6; + break; + case PATH_CUBIC: + last = 8; + break; + default: + console.log("unknown " + (recType == REC_TYPE_PATH ? "REC_TYPE_PATH" + : "REC_TYPE_COMPUTED") + " frag type:" + fragType); + throw "stop execution"; + } + if (recType == REC_TYPE_PATH) { + hasPath = true; + } + break; + case REC_TYPE_ACTIVE: + if (firstActiveSpan < 0) { + firstActiveSpan = tIndex; + } + first = 1; + switch (fragType) { + case ACTIVE_LINE_SPAN: + last = 5; + break; + case ACTIVE_QUAD_SPAN: + last = 7; + break; + case ACTIVE_CUBIC_SPAN: + last = 9; + break; + default: + console.log("unknown REC_TYPE_ACTIVE frag type: " + fragType); + throw "stop execution"; + } + break; + case REC_TYPE_ADD: + switch (fragType) { + case ADD_MOVETO: + break; + case ADD_LINETO: + last = 4; + break; + case ADD_QUADTO: + last = 6; + break; + case ADD_CUBICTO: + last = 8; + break; + case ADD_CLOSE: + case ADD_FILL: + break; + default: + console.log("unknown REC_TYPE_ADD frag type: " + fragType); + throw "stop execution"; + } + break; + case REC_TYPE_SECT: + switch (fragType) { + case INTERSECT_LINE: + first = 1; last = 5; first2 = 8; last2 = 12; + break; + case INTERSECT_LINE_2: + first = 1; last = 5; first2 = 11; last2 = 15; + break; + case INTERSECT_LINE_NO: + first = 0; last = 4; first2 = 4; last2 = 8; + break; + case INTERSECT_QUAD_LINE: + first = 1; last = 7; first2 = 10; last2 = 14; + break; + case INTERSECT_QUAD_LINE_2: + first = 1; last = 7; first2 = 13; last2 = 17; + break; + case INTERSECT_QUAD_LINE_NO: + first = 0; last = 6; first2 = 6; last2 = 10; + break; + case INTERSECT_QUAD: + first = 1; last = 7; first2 = 10; last2 = 16; + break; + case INTERSECT_QUAD_2: + first = 1; last = 7; first2 = 13; last2 = 19; + break; + case INTERSECT_QUAD_NO: + first = 0; last = 6; first2 = 6; last2 = 12; + break; + case INTERSECT_SELF_CUBIC: + first = 1; last = 9; + break; + case INTERSECT_SELF_CUBIC_NO: + first = 0; last = 8; + break; + case INTERSECT_CUBIC_LINE: + first = 1; last = 9; first2 = 12; last2 = 16; + break; + case INTERSECT_CUBIC_LINE_2: + first = 1; last = 9; first2 = 15; last2 = 19; + break; + case INTERSECT_CUBIC_LINE_3: + first = 1; last = 9; first2 = 18; last2 = 22; + break; + case INTERSECT_CUBIC_LINE_NO: + first = 0; last = 8; first2 = 8; last2 = 12; + break; + case INTERSECT_CUBIC_QUAD: + first = 1; last = 9; first2 = 12; last2 = 18; + break; + case INTERSECT_CUBIC_QUAD_2: + first = 1; last = 9; first2 = 15; last2 = 21; + break; + case INTERSECT_CUBIC_QUAD_3: + first = 1; last = 9; first2 = 18; last2 = 24; + break; + case INTERSECT_CUBIC_QUAD_4: + first = 1; last = 9; first2 = 21; last2 = 27; + break; + case INTERSECT_CUBIC_QUAD_NO: + first = 0; last = 8; first2 = 8; last2 = 14; + break; + case INTERSECT_CUBIC: + first = 1; last = 9; first2 = 12; last2 = 20; + break; + case INTERSECT_CUBIC_2: + first = 1; last = 9; first2 = 15; last2 = 23; + break; + case INTERSECT_CUBIC_3: + first = 1; last = 9; first2 = 18; last2 = 26; + break; + case INTERSECT_CUBIC_4: + first = 1; last = 9; first2 = 21; last2 = 29; + break; + case INTERSECT_CUBIC_NO: + first = 0; last = 8; first2 = 8; last2 = 16; + break; + default: + console.log("unknown REC_TYPE_SECT frag type: " + fragType); + throw "stop execution"; + } + break; + default: + continue; + } + for (var idx = first; idx < last; idx += 2) { + xmin = Math.min(xmin, frags[idx]); + xmax = Math.max(xmax, frags[idx]); + ymin = Math.min(ymin, frags[idx + 1]); + ymax = Math.max(ymax, frags[idx + 1]); + } + for (var idx = first2; idx < last2; idx += 2) { + xmin = Math.min(xmin, frags[idx]); + xmax = Math.max(xmax, frags[idx]); + ymin = Math.min(ymin, frags[idx + 1]); + ymax = Math.max(ymax, frags[idx + 1]); + } + } + } + var angleBounds = [Infinity, Infinity, -Infinity, -Infinity]; + for (var tIndex = 0; tIndex < test.length; tIndex += 3) { + var recType = test[tIndex]; + var records = test[tIndex + 2]; + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + var frags = records[recordIndex + 1]; + switch (recType) { + case REC_TYPE_ACTIVE_OP: + if (!draw_op) { + break; + } + { + var curve = curvePartialByID(test, frags[0], frags[1], frags[2]); + curve_extremes(curve, angleBounds); + } + break; + case REC_TYPE_ANGLE: + if (!draw_angle) { + break; + } + if (fragType == ANGLE_AFTER) { + var curve = curvePartialByID(test, frags[0], frags[3], frags[4]); + curve_extremes(curve, angleBounds); + curve = curvePartialByID(test, frags[5], frags[8], frags[9]); + curve_extremes(curve, angleBounds); + curve = curvePartialByID(test, frags[10], frags[13], frags[14]); + } else if (fragType == ANGLE_AFTER2) { + var curve = curvePartialByID(test, frags[0], frags[4], frags[5]); + curve_extremes(curve, angleBounds); + curve = curvePartialByID(test, frags[6], frags[10], frags[11]); + curve_extremes(curve, angleBounds); + curve = curvePartialByID(test, frags[12], frags[16], frags[17]); + } + break; + case REC_TYPE_SORT: + if (!draw_sort) { + break; + } + if (fragType == SORT_UNARY || fragType == SORT_BINARY) { + var curve = curvePartialByID(test, frags[0], frags[6], frags[8]); + curve_extremes(curve, angleBounds); + } + break; + } + } + } + xmin = Math.min(xmin, angleBounds[0]); + ymin = Math.min(ymin, angleBounds[1]); + xmax = Math.max(xmax, angleBounds[2]); + ymax = Math.max(ymax, angleBounds[3]); + setScale(xmin, xmax, ymin, ymax); + if (hasPath == false && hasComputedPath == true && !draw_computed) { + draw_computed = 3; // show both quadratics and cubics + } + if (hasPath == true && hasComputedPath == false && draw_computed) { + draw_computed = 0; + } +} + +function curveByID(test, id) { + var tIndex = firstActiveSpan; + if (tIndex < 0) { + return []; + } + while (tIndex < test.length) { + var recType = test[tIndex]; + if (recType != REC_TYPE_ACTIVE) { + return []; + } + var records = test[tIndex + 2]; + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + var frags = records[recordIndex + 1]; + if (frags[0] == id) { + switch (fragType) { + case ACTIVE_LINE_SPAN: + return [frags[1], frags[2], frags[3], frags[4]]; + case ACTIVE_QUAD_SPAN: + return [frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6]]; + case ACTIVE_CUBIC_SPAN: + return [frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6], frags[7], frags[8]]; + } + } + } + tIndex += 3; + } + return []; +} + +function curvePartialByID(test, id, t0, t1) { + var tIndex = firstActiveSpan; + if (tIndex < 0) { + return []; + } + while (tIndex < test.length) { + var recType = test[tIndex]; + if (recType != REC_TYPE_ACTIVE) { + return []; + } + var records = test[tIndex + 2]; + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + var frags = records[recordIndex + 1]; + if (frags[0] == id) { + switch (fragType) { + case ACTIVE_LINE_SPAN: + return linePartial(frags[1], frags[2], frags[3], frags[4], t0, t1); + case ACTIVE_QUAD_SPAN: + return quadPartial(frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6], t0, t1); + case ACTIVE_CUBIC_SPAN: + return cubicPartial(frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6], frags[7], frags[8], t0, t1); + } + } + } + tIndex += 3; + } + return []; +} + +function idByCurve(test, frag, type) { + var tIndex = firstActiveSpan; + if (tIndex < 0) { + return -1; + } + while (tIndex < test.length) { + var recType = test[tIndex]; + if (recType != REC_TYPE_ACTIVE) { + return -1; + } + var records = test[tIndex + 2]; + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + var frags = records[recordIndex + 1]; + switch (fragType) { + case ACTIVE_LINE_SPAN: + if (type != PATH_LINE) { + continue; + } + if (frag[0] != frags[1] || frag[1] != frags[2] + || frag[2] != frags[3] || frag[3] != frags[4]) { + continue; + } + return frags[0]; + case ACTIVE_QUAD_SPAN: + if (type != PATH_QUAD) { + continue; + } + if (frag[0] != frags[1] || frag[1] != frags[2] + || frag[2] != frags[3] || frag[3] != frags[4] + || frag[4] != frags[5] || frag[5] != frags[6]) { + continue; + } + return frags[0]; + case ACTIVE_CUBIC_SPAN: + if (type != PATH_CUBIC) { + continue; + } + if (frag[0] != frags[1] || frag[1] != frags[2] + || frag[2] != frags[3] || frag[3] != frags[4] + || frag[4] != frags[5] || frag[5] != frags[6] + || frag[6] != frags[7] || frag[7] != frags[8]) { + continue; + } + return frags[0]; + } + } + ++tIndex; + } + return -1; +} + +function curve_extremes(curve, bounds) { + for (var index = 0; index < curve.length; index += 2) { + var x = curve[index]; + var y = curve[index + 1]; + bounds[0] = Math.min(bounds[0], x); + bounds[1] = Math.min(bounds[1], y); + bounds[2] = Math.max(bounds[2], x); + bounds[3] = Math.max(bounds[3], y); + } +} + +function setScale(x0, x1, y0, y1) { + var srcWidth = x1 - x0; + var srcHeight = y1 - y0; + var usableWidth = screenWidth; + var xDigits = Math.ceil(Math.log(Math.abs(xmax)) / Math.log(10)); + var yDigits = Math.ceil(Math.log(Math.abs(ymax)) / Math.log(10)); + usableWidth -= (xDigits + yDigits) * 10; + usableWidth -= decimal_places * 10; + if (draw_legend) { + usableWidth -= 40; + } + var hscale = usableWidth / srcWidth; + var vscale = screenHeight / srcHeight; + scale = Math.min(hscale, vscale); + var invScale = 1 / scale; + var sxmin = x0 - invScale * 5; + var symin = y0 - invScale * 10; + var sxmax = x1 + invScale * (6 * decimal_places + 10); + var symax = y1 + invScale * 10; + srcWidth = sxmax - sxmin; + srcHeight = symax - symin; + hscale = usableWidth / srcWidth; + vscale = screenHeight / srcHeight; + scale = Math.min(hscale, vscale); + srcLeft = sxmin; + srcTop = symin; +} + +function drawArc(curve, op, from, to) { + var type = PATH_LINE + (curve.length / 2 - 2); + var pt = pointAtT(curve, type, op ? 0.4 : 0.6); + var dy = pt.y - curve[1]; + var dx = pt.x - curve[0]; + var dist = Math.sqrt(dy * dy + dx * dx); + var _dist = dist * scale; + var angle = Math.atan2(dy, dx); + var _px = (curve[0] - srcLeft) * scale; + var _py = (curve[1] - srcTop) * scale; + var divisor = 4; + var endDist; + do { + var ends = []; + for (var index = -1; index <= 1; index += 2) { + var px = Math.cos(index * Math.PI / divisor); + var py = Math.sin(index * Math.PI / divisor); + ends.push(px); + ends.push(py); + } + var endDx = (ends[2] - ends[0]) * scale * dist; + var endDy = (ends[3] - ends[1]) * scale * dist; + endDist = Math.sqrt(endDx * endDx + endDy * endDy); + if (endDist < 100) { + break; + } + divisor *= 2; + } while (true); + if (endDist < 30) { + return; + } + if (op) { + divisor *= 2; + } + ctx.strokeStyle = op ? "rgba(210,0,45, 0.4)" : "rgba(90,90,90, 0.5)"; + ctx.beginPath(); + ctx.arc(_px, _py, _dist, angle - Math.PI / divisor, angle + Math.PI / divisor, false); + ctx.stroke(); + var saveAlign = ctx.textAlign; + var saveStyle = ctx.fillStyle; + var saveFont = ctx.font; + ctx.textAlign = "center"; + ctx.fillStyle = "black"; + ctx.font = "normal 24px Arial"; + divisor *= 0.8; + for (var index = -1; index <= 1; index += 2) { + var px = curve[0] + Math.cos(angle + index * Math.PI / divisor) * dist; + var py = curve[1] + Math.sin(angle + index * Math.PI / divisor) * dist; + var _px = (px - srcLeft) * scale; + var _py = (py - srcTop) * scale; + ctx.fillText(index < 0 ? to.toString() : from.toString(), _px, _py + 8); + } + ctx.textAlign = saveAlign; + ctx.fillStyle = saveStyle; + ctx.font = saveFont; +} + +function drawPoint(px, py, end) { + for (var pts = 0; pts < drawnPts.length; pts += 2) { + var x = drawnPts[pts]; + var y = drawnPts[pts + 1]; + if (px == x && py == y) { + return; + } + } + drawnPts.push(px); + drawnPts.push(py); + var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places); + var _px = (px - srcLeft) * scale; + var _py = (py - srcTop) * scale; + ctx.beginPath(); + ctx.arc(_px, _py, 3, 0, Math.PI*2, true); + ctx.closePath(); + if (end) { + ctx.fill(); + } else { + ctx.stroke(); + } + if (debug_xy) { + ctx.textAlign = "left"; + ctx.fillText(label, _px + 5, _py); + } +} + +function drawPoints(ptArray, curveType, drawControls) { + var count = (curveType - PATH_LINE + 2) * 2; + for (var idx = 0; idx < count; idx += 2) { + if (!drawControls && idx != 0 && idx != count - 2) { + continue; + } + drawPoint(ptArray[idx], ptArray[idx + 1], idx == 0 || idx == count - 2); + } +} + +function drawControlLines(curve, curveType, drawEnd) { + if (curveType == PATH_LINE) { + return; + } + ctx.strokeStyle = "rgba(0,0,0, 0.3)"; + drawLine(curve[0], curve[1], curve[2], curve[3]); + drawLine(curve[2], curve[3], curve[4], curve[5]); + if (curveType == PATH_CUBIC) { + drawLine(curve[4], curve[5], curve[6], curve[7]); + if (drawEnd > 1) { + drawLine(curve[6], curve[7], curve[0], curve[1]); + if (drawEnd > 2) { + drawLine(curve[0], curve[1], curve[4], curve[5]); + drawLine(curve[6], curve[7], curve[2], curve[3]); + } + } + } else if (drawEnd > 1) { + drawLine(curve[4], curve[5], curve[0], curve[1]); + } +} + +function pointAtT(curve, curveType, t) { + var xy = {}; + switch (curveType) { + case PATH_LINE: + var a = 1 - t; + var b = t; + xy.x = a * curve[0] + b * curve[2]; + xy.y = a * curve[1] + b * curve[3]; + break; + case PATH_QUAD: + var one_t = 1 - t; + var a = one_t * one_t; + var b = 2 * one_t * t; + var c = t * t; + xy.x = a * curve[0] + b * curve[2] + c * curve[4]; + xy.y = a * curve[1] + b * curve[3] + c * curve[5]; + break; + case PATH_CUBIC: + var one_t = 1 - t; + var one_t2 = one_t * one_t; + var a = one_t2 * one_t; + var b = 3 * one_t2 * t; + var t2 = t * t; + var c = 3 * one_t * t2; + var d = t2 * t; + xy.x = a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6]; + xy.y = a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7]; + break; + } + return xy; +} + +function drawPointAtT(curve, curveType) { + var x, y; + var xy = pointAtT(curve, curveType, curveT); + drawPoint(xy.x, xy.y, true); + if (!draw_intersectT) { + return; + } + ctx.fillStyle = "red"; + drawTAtPointUp(xy.x, xy.y, curveT); +} + +function drawTAtPointUp(px, py, t) { + var label = t.toFixed(decimal_places); + var _px = (px - srcLeft)* scale; + var _py = (py - srcTop) * scale; + ctx.fillText(label, _px + 5, _py - 10); +} + +function drawTAtPointDown(px, py, t) { + var label = t.toFixed(decimal_places); + var _px = (px - srcLeft)* scale; + var _py = (py - srcTop) * scale; + ctx.fillText(label, _px + 5, _py + 10); +} + +function alreadyDrawnLine(x1, y1, x2, y2) { + if (collect_bounds) { + if (focus_enabled) { + focusXmin = Math.min(focusXmin, x1, x2); + focusYmin = Math.min(focusYmin, y1, y2); + focusXmax = Math.max(focusXmax, x1, x2); + focusYmax = Math.max(focusYmax, y1, y2); + } + return true; + } + for (var pts = 0; pts < drawnLines.length; pts += 4) { + if (x1 == drawnLines[pts] && y1 == drawnLines[pts + 1] + && x2 == drawnLines[pts + 2] && y2 == drawnLines[pts + 3]) { + return true; + } + } + drawnLines.push(x1); + drawnLines.push(y1); + drawnLines.push(x2); + drawnLines.push(y2); + return false; +} + +function drawLine(x1, y1, x2, y2) { + if (alreadyDrawnLine(x1, y1, x2, y2)) { + return; + } + ctx.beginPath(); + ctx.moveTo((x1 - srcLeft) * scale, + (y1 - srcTop) * scale); + ctx.lineTo((x2 - srcLeft) * scale, + (y2 - srcTop) * scale); + ctx.stroke(); +} + +function linePartial(x1, y1, x2, y2, t1, t2) { + var dx = x1 - x2; + var dy = y1 - y2; + var array = [ + x1 - t1 * dx, + y1 - t1 * dy, + x1 - t2 * dx, + y1 - t2 * dy + ]; + return array; +} + +function drawLinePartial(x1, y1, x2, y2, t1, t2) { + var a = linePartial(x1, y1, x2, y2, t1, t2); + var ax = a[0]; + var ay = a[1]; + var bx = a[2]; + var by = a[3]; + if (alreadyDrawnLine(ax, ay, bx, by)) { + return; + } + ctx.beginPath(); + ctx.moveTo((ax - srcLeft) * scale, + (ay - srcTop) * scale); + ctx.lineTo((bx - srcLeft) * scale, + (by - srcTop) * scale); + ctx.stroke(); +} + +function alreadyDrawnQuad(x1, y1, x2, y2, x3, y3) { + if (collect_bounds) { + if (focus_enabled) { + focusXmin = Math.min(focusXmin, x1, x2, x3); + focusYmin = Math.min(focusYmin, y1, y2, y3); + focusXmax = Math.max(focusXmax, x1, x2, x3); + focusYmax = Math.max(focusYmax, y1, y2, y3); + } + return true; + } + for (var pts = 0; pts < drawnQuads.length; pts += 6) { + if (x1 == drawnQuads[pts] && y1 == drawnQuads[pts + 1] + && x2 == drawnQuads[pts + 2] && y2 == drawnQuads[pts + 3] + && x3 == drawnQuads[pts + 4] && y3 == drawnQuads[pts + 5]) { + return true; + } + } + drawnQuads.push(x1); + drawnQuads.push(y1); + drawnQuads.push(x2); + drawnQuads.push(y2); + drawnQuads.push(x3); + drawnQuads.push(y3); + return false; +} + +function drawQuad(x1, y1, x2, y2, x3, y3) { + if (alreadyDrawnQuad(x1, y1, x2, y2, x3, y3)) { + return; + } + ctx.beginPath(); + ctx.moveTo((x1 - srcLeft) * scale, + (y1 - srcTop) * scale); + ctx.quadraticCurveTo((x2 - srcLeft) * scale, + (y2 - srcTop) * scale, + (x3 - srcLeft) * scale, + (y3 - srcTop) * scale); + ctx.stroke(); +} + +function interp(A, B, t) { + return A + (B - A) * t; +} + +function interp_quad_coords(x1, x2, x3, t) +{ + var ab = interp(x1, x2, t); + var bc = interp(x2, x3, t); + var abc = interp(ab, bc, t); + return abc; +} + +function quadPartial(x1, y1, x2, y2, x3, y3, t1, t2) { + var ax = interp_quad_coords(x1, x2, x3, t1); + var ay = interp_quad_coords(y1, y2, y3, t1); + var dx = interp_quad_coords(x1, x2, x3, (t1 + t2) / 2); + var dy = interp_quad_coords(y1, y2, y3, (t1 + t2) / 2); + var cx = interp_quad_coords(x1, x2, x3, t2); + var cy = interp_quad_coords(y1, y2, y3, t2); + var bx = 2*dx - (ax + cx)/2; + var by = 2*dy - (ay + cy)/2; + var array = [ + ax, ay, bx, by, cx, cy + ]; + return array; +} + +function drawQuadPartial(x1, y1, x2, y2, x3, y3, t1, t2) { + var a = quadPartial(x1, y1, x2, y2, x3, y3, t1, t2); + var ax = a[0]; + var ay = a[1]; + var bx = a[2]; + var by = a[3]; + var cx = a[4]; + var cy = a[5]; + if (alreadyDrawnQuad(ax, ay, bx, by, cx, cy)) { + return; + } + ctx.beginPath(); + ctx.moveTo((ax - srcLeft) * scale, + (ay - srcTop) * scale); + ctx.quadraticCurveTo((bx - srcLeft) * scale, + (by - srcTop) * scale, + (cx - srcLeft) * scale, + (cy - srcTop) * scale); + ctx.stroke(); +} + +function alreadyDrawnCubic(x1, y1, x2, y2, x3, y3, x4, y4) { + if (collect_bounds) { + if (focus_enabled) { + focusXmin = Math.min(focusXmin, x1, x2, x3, x4); + focusYmin = Math.min(focusYmin, y1, y2, y3, y4); + focusXmax = Math.max(focusXmax, x1, x2, x3, x4); + focusYmax = Math.max(focusYmax, y1, y2, y3, y4); + } + return true; + } + for (var pts = 0; pts < drawnCubics.length; pts += 8) { + if (x1 == drawnCubics[pts] && y1 == drawnCubics[pts + 1] + && x2 == drawnCubics[pts + 2] && y2 == drawnCubics[pts + 3] + && x3 == drawnCubics[pts + 4] && y3 == drawnCubics[pts + 5] + && x4 == drawnCubics[pts + 6] && y4 == drawnCubics[pts + 7]) { + return true; + } + } + drawnCubics.push(x1); + drawnCubics.push(y1); + drawnCubics.push(x2); + drawnCubics.push(y2); + drawnCubics.push(x3); + drawnCubics.push(y3); + drawnCubics.push(x4); + drawnCubics.push(y4); + return false; +} + +function drawCubic(x1, y1, x2, y2, x3, y3, x4, y4) { + if (alreadyDrawnCubic(x1, y1, x2, y2, x3, y3, x4, y4)) { + return; + } + ctx.beginPath(); + ctx.moveTo((x1 - srcLeft) * scale, + (y1 - srcTop) * scale); + ctx.bezierCurveTo((x2 - srcLeft) * scale, + (y2 - srcTop) * scale, + (x3 - srcLeft) * scale, + (y3 - srcTop) * scale, + (x4 - srcLeft) * scale, + (y4 - srcTop) * scale); + ctx.stroke(); +} + +function interp_cubic_coords(x1, x2, x3, x4, t) +{ + var ab = interp(x1, x2, t); + var bc = interp(x2, x3, t); + var cd = interp(x3, x4, t); + var abc = interp(ab, bc, t); + var bcd = interp(bc, cd, t); + var abcd = interp(abc, bcd, t); + return abcd; +} + +function cubicPartial(x1, y1, x2, y2, x3, y3, x4, y4, t1, t2) { + var ax = interp_cubic_coords(x1, x2, x3, x4, t1); + var ay = interp_cubic_coords(y1, y2, y3, y4, t1); + var ex = interp_cubic_coords(x1, x2, x3, x4, (t1*2+t2)/3); + var ey = interp_cubic_coords(y1, y2, y3, y4, (t1*2+t2)/3); + var fx = interp_cubic_coords(x1, x2, x3, x4, (t1+t2*2)/3); + var fy = interp_cubic_coords(y1, y2, y3, y4, (t1+t2*2)/3); + var dx = interp_cubic_coords(x1, x2, x3, x4, t2); + var dy = interp_cubic_coords(y1, y2, y3, y4, t2); + var mx = ex * 27 - ax * 8 - dx; + var my = ey * 27 - ay * 8 - dy; + var nx = fx * 27 - ax - dx * 8; + var ny = fy * 27 - ay - dy * 8; + var bx = (mx * 2 - nx) / 18; + var by = (my * 2 - ny) / 18; + var cx = (nx * 2 - mx) / 18; + var cy = (ny * 2 - my) / 18; + var array = [ + ax, ay, bx, by, cx, cy, dx, dy + ]; + return array; +} + +function drawCubicPartial(x1, y1, x2, y2, x3, y3, x4, y4, t1, t2) { + var a = cubicPartial(x1, y1, x2, y2, x3, y3, x4, y4, t1, t2); + var ax = a[0]; + var ay = a[1]; + var bx = a[2]; + var by = a[3]; + var cx = a[4]; + var cy = a[5]; + var dx = a[6]; + var dy = a[7]; + if (alreadyDrawnCubic(ax, ay, bx, by, cx, cy, dx, dy)) { + return; + } + ctx.beginPath(); + ctx.moveTo((ax - srcLeft) * scale, + (ay - srcTop) * scale); + ctx.bezierCurveTo((bx - srcLeft) * scale, + (by - srcTop) * scale, + (cx - srcLeft) * scale, + (cy - srcTop) * scale, + (dx - srcLeft) * scale, + (dy - srcTop) * scale); + ctx.stroke(); +} + +function drawCurve(c) { + switch (c.length) { + case 4: + drawLine(c[0], c[1], c[2], c[3]); + break; + case 6: + drawQuad(c[0], c[1], c[2], c[3], c[4], c[5]); + break; + case 8: + drawCubic(c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]); + break; + } +} + +function boundsWidth(pts) { + var min = pts[0]; + var max = pts[0]; + for (var idx = 2; idx < pts.length; idx += 2) { + min = Math.min(min, pts[idx]); + max = Math.max(max, pts[idx]); + } + return max - min; +} + +function boundsHeight(pts) { + var min = pts[1]; + var max = pts[1]; + for (var idx = 3; idx < pts.length; idx += 2) { + min = Math.min(min, pts[idx]); + max = Math.max(max, pts[idx]); + } + return max - min; +} + +function tangent(pts) { + var dx = pts[2] - pts[0]; + var dy = pts[3] - pts[1]; + if (dx == 0 && dy == 0 && pts.length > 4) { + dx = pts[4] - pts[0]; + dy = pts[5] - pts[1]; + if (dx == 0 && dy == 0 && pts.length > 6) { + dx = pts[6] - pts[0]; + dy = pts[7] - pts[1]; + } + } + return Math.atan2(-dy, dx); +} + +function hodograph(cubic) { + var hodo = []; + hodo[0] = 3 * (cubic[2] - cubic[0]); + hodo[1] = 3 * (cubic[3] - cubic[1]); + hodo[2] = 3 * (cubic[4] - cubic[2]); + hodo[3] = 3 * (cubic[5] - cubic[3]); + hodo[4] = 3 * (cubic[6] - cubic[4]); + hodo[5] = 3 * (cubic[7] - cubic[5]); + return hodo; +} + +function hodograph2(cubic) { + var quad = hodograph(cubic); + var hodo = []; + hodo[0] = 2 * (quad[2] - quad[0]); + hodo[1] = 2 * (quad[3] - quad[1]); + hodo[2] = 2 * (quad[4] - quad[2]); + hodo[3] = 2 * (quad[5] - quad[3]); + return hodo; +} + +function quadraticRootsReal(A, B, C, s) { + if (A == 0) { + if (B == 0) { + s[0] = 0; + return C == 0; + } + s[0] = -C / B; + return 1; + } + /* normal form: x^2 + px + q = 0 */ + var p = B / (2 * A); + var q = C / A; + var p2 = p * p; + if (p2 < q) { + return 0; + } + var sqrt_D = 0; + if (p2 > q) { + sqrt_D = sqrt(p2 - q); + } + s[0] = sqrt_D - p; + s[1] = -sqrt_D - p; + return 1 + s[0] != s[1]; +} + +function add_valid_ts(s, realRoots, t) { + var foundRoots = 0; + for (var index = 0; index < realRoots; ++index) { + var tValue = s[index]; + if (tValue >= 0 && tValue <= 1) { + for (var idx2 = 0; idx2 < foundRoots; ++idx2) { + if (t[idx2] != tValue) { + t[foundRoots++] = tValue; + } + } + } + } + return foundRoots; +} + +function quadraticRootsValidT(a, b, c, t) { + var s = []; + var realRoots = quadraticRootsReal(A, B, C, s); + var foundRoots = add_valid_ts(s, realRoots, t); + return foundRoots != 0; +} + +function find_cubic_inflections(cubic, tValues) { + var Ax = src[2] - src[0]; + var Ay = src[3] - src[1]; + var Bx = src[4] - 2 * src[2] + src[0]; + var By = src[5] - 2 * src[3] + src[1]; + var Cx = src[6] + 3 * (src[2] - src[4]) - src[0]; + var Cy = src[7] + 3 * (src[3] - src[5]) - src[1]; + return quadraticRootsValidT(Bx * Cy - By * Cx, (Ax * Cy - Ay * Cx), + Ax * By - Ay * Bx, tValues); +} + +function dxy_at_t(curve, type, t) { + var dxy = {}; + if (type == PATH_QUAD) { + var a = t - 1; + var b = 1 - 2 * t; + var c = t; + dxy.x = a * curve[0] + b * curve[2] + c * curve[4]; + dxy.y = a * curve[1] + b * curve[3] + c * curve[5]; + } else if (type == PATH_CUBIC) { + var one_t = 1 - t; + var a = curve[0]; + var b = curve[2]; + var c = curve[4]; + var d = curve[6]; + dxy.x = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t); + a = curve[1]; + b = curve[3]; + c = curve[5]; + d = curve[7]; + dxy.y = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t); + } + return dxy; +} + +function drawLabel(num, px, py) { + ctx.beginPath(); + ctx.arc(px, py, 8, 0, Math.PI*2, true); + ctx.closePath(); + ctx.strokeStyle = "rgba(0,0,0, 0.4)"; + ctx.lineWidth = num == 0 || num == 3 ? 2 : 1; + ctx.stroke(); + ctx.fillStyle = "black"; + ctx.font = "normal 10px Arial"; + // ctx.rotate(0.001); + ctx.fillText(num, px - 2, py + 3); + // ctx.rotate(-0.001); +} + +function drawLabelX(ymin, num, loc) { + var px = (loc - srcLeft) * scale; + var py = (ymin - srcTop) * scale - 20; + drawLabel(num, px, py); +} + +function drawLabelY(xmin, num, loc) { + var px = (xmin - srcLeft) * scale - 20; + var py = (loc - srcTop) * scale; + drawLabel(num, px, py); +} + +function drawHodoOrigin(hx, hy, hMinX, hMinY, hMaxX, hMaxY) { + ctx.beginPath(); + ctx.moveTo(hx, hy - 100); + ctx.lineTo(hx, hy); + ctx.strokeStyle = hMinY < 0 ? "green" : "blue"; + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(hx, hy); + ctx.lineTo(hx, hy + 100); + ctx.strokeStyle = hMaxY > 0 ? "green" : "blue"; + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(hx - 100, hy); + ctx.lineTo(hx, hy); + ctx.strokeStyle = hMinX < 0 ? "green" : "blue"; + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(hx, hy); + ctx.lineTo(hx + 100, hy); + ctx.strokeStyle = hMaxX > 0 ? "green" : "blue"; + ctx.stroke(); +} + +function scalexy(x, y, mag) { + var length = Math.sqrt(x * x + y * y); + return mag / length; +} + +function drawArrow(x, y, dx, dy) { + var dscale = scalexy(dx, dy, 1 / scale * 100); + dx *= dscale; + dy *= dscale; + ctx.beginPath(); + ctx.moveTo((x - srcLeft) * scale, (y - srcTop) * scale); + x += dx; + y += dy; + ctx.lineTo((x - srcLeft) * scale, (y - srcTop) * scale); + dx /= 10; + dy /= 10; + ctx.lineTo((x - dy - srcLeft) * scale, (y + dx - srcTop) * scale); + ctx.lineTo((x + dx * 2 - srcLeft) * scale, (y + dy * 2 - srcTop) * scale); + ctx.lineTo((x + dy - srcLeft) * scale, (y - dx - srcTop) * scale); + ctx.lineTo((x - srcLeft) * scale, (y - srcTop) * scale); + ctx.strokeStyle = "rgba(0,75,0, 0.4)"; + ctx.stroke(); +} + +function x_at_t(curve, t) { + var one_t = 1 - t; + if (curve.length == 4) { + return one_t * curve[0] + t * curve[2]; + } + var one_t2 = one_t * one_t; + var t2 = t * t; + if (curve.length == 6) { + return one_t2 * curve[0] + 2 * one_t * t * curve[2] + t2 * curve[4]; + } + var a = one_t2 * one_t; + var b = 3 * one_t2 * t; + var c = 3 * one_t * t2; + var d = t2 * t; + return a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6]; +} + +function y_at_t(curve, t) { + var one_t = 1 - t; + if (curve.length == 4) { + return one_t * curve[1] + t * curve[3]; + } + var one_t2 = one_t * one_t; + var t2 = t * t; + if (curve.length == 6) { + return one_t2 * curve[1] + 2 * one_t * t * curve[3] + t2 * curve[5]; + } + var a = one_t2 * one_t; + var b = 3 * one_t2 * t; + var c = 3 * one_t * t2; + var d = t2 * t; + return a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7]; +} + +function drawOrder(curve, label) { + var px = x_at_t(curve, 0.75); + var py = y_at_t(curve, 0.75); + var _px = (px - srcLeft) * scale; + var _py = (py - srcTop) * scale; + ctx.beginPath(); + ctx.arc(_px, _py, 15, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.fillStyle = "white"; + ctx.fill(); + if (label == 'L') { + ctx.strokeStyle = "rgba(255,0,0, 1)"; + ctx.fillStyle = "rgba(255,0,0, 1)"; + } else { + ctx.strokeStyle = "rgba(0,0,255, 1)"; + ctx.fillStyle = "rgba(0,0,255, 1)"; + } + ctx.stroke(); + ctx.font = "normal 16px Arial"; + ctx.textAlign = "center"; + ctx.fillText(label, _px, _py + 5); + ctx.font = "normal 10px Arial"; +} + +function drawID(curve, id) { + var px = x_at_t(curve, 0.5); + var py = y_at_t(curve, 0.5); + var _px = (px - srcLeft) * scale; + var _py = (py - srcTop) * scale; + draw_id_at(id, _px, _py); +} + +function draw_id_at(id, _px, _py) { + ctx.beginPath(); + ctx.arc(_px, _py, 15, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.fillStyle = "white"; + ctx.fill(); + ctx.strokeStyle = "rgba(127,127,0, 1)"; + ctx.fillStyle = "rgba(127,127,0, 1)"; + ctx.stroke(); + ctx.font = "normal 16px Arial"; + ctx.textAlign = "center"; + ctx.fillText(id, _px, _py + 5); + ctx.font = "normal 10px Arial"; +} + +function drawLinePartialID(id, x1, y1, x2, y2, t1, t2) { + var curve = [x1, y1, x2, y2]; + drawCurvePartialID(id, curve, t1, t2); +} + +function drawQuadPartialID(id, x1, y1, x2, y2, x3, y3, t1, t2) { + var curve = [x1, y1, x2, y2, x3, y3]; + drawCurvePartialID(id, curve, t1, t2); +} + +function drawCubicPartialID(id, x1, y1, x2, y2, x3, y3, x4, y4, t1, t2) { + var curve = [x1, y1, x2, y2, x3, y3, x4, y4]; + drawCurvePartialID(id, curve, t1, t2); +} + +function drawCurvePartialID(id, curve, t1, t2) { + var px = x_at_t(curve, (t1 + t2) / 2); + var py = y_at_t(curve, (t1 + t2) / 2); + var _px = (px - srcLeft) * scale; + var _py = (py - srcTop) * scale; + draw_id_at(id, _px, _py); +} + +function drawCurveSpecials(test, curve, type) { + if (pt_labels) { + drawPoints(curve, type, pt_labels == 2); + } + if (control_lines != 0) { + drawControlLines(curve, type, control_lines); + } + if (curve_t) { + drawPointAtT(curve, type); + } + if (draw_midpoint) { + var mid = pointAtT(curve, type, 0.5); + drawPoint(mid.x, mid.y, true); + } + if (draw_id) { + var id = idByCurve(test, curve, type); + if (id >= 0) { + drawID(curve, id); + } + } + if (type == PATH_LINE) { + return; + } + if (draw_deriviatives > 0) { + var d = dxy_at_t(curve, type, 0); + drawArrow(curve[0], curve[1], d.x, d.y); + if (draw_deriviatives == 2) { + d = dxy_at_t(curve, type, 1); + if (type == PATH_CUBIC) { + drawArrow(curve[6], curve[7], d.x, d.y); + } else { + drawArrow(curve[4], curve[5], d.x, d.y); + } + } + if (draw_midpoint) { + var mid = pointAtT(curve, type, 0.5); + d = dxy_at_t(curve, type, 0.5); + drawArrow(mid.x, mid.y, d.x, d.y); + } + } + if (type != PATH_CUBIC) { + return; + } + if (draw_hodo == 1 || draw_hodo == 2) { + var hodo = hodograph(curve); + var hMinX = Math.min(0, hodo[0], hodo[2], hodo[4]); + var hMinY = Math.min(0, hodo[1], hodo[3], hodo[5]); + var hMaxX = Math.max(0, hodo[0], hodo[2], hodo[4]); + var hMaxY = Math.max(0, hodo[1], hodo[3], hodo[5]); + var hScaleX = hMaxX - hMinX > 0 ? screenWidth / (hMaxX - hMinX) : 1; + var hScaleY = hMaxY - hMinY > 0 ? screenHeight / (hMaxY - hMinY) : 1; + var hUnit = Math.min(hScaleX, hScaleY); + hUnit /= 2; + var hx = xoffset - hMinX * hUnit; + var hy = yoffset - hMinY * hUnit; + ctx.moveTo(hx + hodo[0] * hUnit, hy + hodo[1] * hUnit); + ctx.quadraticCurveTo( + hx + hodo[2] * hUnit, hy + hodo[3] * hUnit, + hx + hodo[4] * hUnit, hy + hodo[5] * hUnit); + ctx.strokeStyle = "red"; + ctx.stroke(); + if (draw_hodo == 1) { + drawHodoOrigin(hx, hy, hMinX, hMinY, hMaxX, hMaxY); + } + } + if (draw_hodo == 3) { + var hodo = hodograph2(curve); + var hMinX = Math.min(0, hodo[0], hodo[2]); + var hMinY = Math.min(0, hodo[1], hodo[3]); + var hMaxX = Math.max(0, hodo[0], hodo[2]); + var hMaxY = Math.max(0, hodo[1], hodo[3]); + var hScaleX = hMaxX - hMinX > 0 ? screenWidth / (hMaxX - hMinX) : 1; + var hScaleY = hMaxY - hMinY > 0 ? screenHeight / (hMaxY - hMinY) : 1; + var hUnit = Math.min(hScaleX, hScaleY); + hUnit /= 2; + var hx = xoffset - hMinX * hUnit; + var hy = yoffset - hMinY * hUnit; + ctx.moveTo(hx + hodo[0] * hUnit, hy + hodo[1] * hUnit); + ctx.lineTo(hx + hodo[2] * hUnit, hy + hodo[3] * hUnit); + ctx.strokeStyle = "red"; + ctx.stroke(); + drawHodoOrigin(hx, hy, hMinX, hMinY, hMaxX, hMaxY); + } + if (draw_sequence) { + var ymin = Math.min(curve[1], curve[3], curve[5], curve[7]); + for (var i = 0; i < 8; i+= 2) { + drawLabelX(ymin, i >> 1, curve[i]); + } + var xmin = Math.min(curve[0], curve[2], curve[4], curve[6]); + for (var i = 1; i < 8; i+= 2) { + drawLabelY(xmin, i >> 1, curve[i]); + } + } +} + +function logCurves(test) { + for (curves in test) { + var curve = test[curves]; + dumpCurve(curve); + } +} + +function curveToString(curve) { + var str = "{{"; + for (i = 0; i < curve.length; i += 2) { + str += curve[i].toFixed(decimal_places) + "," + curve[i + 1].toFixed(decimal_places); + if (i < curve.length - 2) { + str += "}, {"; + } + } + str += "}}"; + return str; +} + +function dumpCurve(curve) { + console.log(curveToString(curve)); +} + +function draw(test, lines, title) { + ctx.fillStyle = "rgba(0,0,0, 0.1)"; + ctx.font = "normal 50px Arial"; + ctx.textAlign = "left"; + ctx.fillText(title, 50, 50); + ctx.font = "normal 10px Arial"; + ctx.lineWidth = "1.001"; "0.999"; + var secondPath = test.length; + var closeCount = 0; + logStart = -1; + logRange = 0; + // find last active rec type at this step + var curType = test[0]; + var curStep = 0; + var hasOp = false; + var lastActive = 0; + var lastAdd = 0; + var lastSect = 0; + var lastSort = 0; + var lastMark = 0; + activeCount = 0; + addCount = 0; + angleCount = 0; + opCount = 0; + sectCount = 0; + sortCount = 0; + markCount = 0; + activeMax = 0; + addMax = 0; + angleMax = 0; + opMax = 0; + sectMax = 0; + sectMax2 = 0; + sortMax = 0; + markMax = 0; + lastIndex = test.length - 3; + for (var tIndex = 0; tIndex < test.length; tIndex += 3) { + var recType = test[tIndex]; + if (!typeof recType == 'number' || recType < REC_TYPE_UNKNOWN || recType > REC_TYPE_LAST) { + console.log("unknown rec type: " + recType); + throw "stop execution"; + } + // if (curType == recType && curType != REC_TYPE_ADD) { + // continue; + // } + var inStepRange = step_limit == 0 || curStep < step_limit; + curType = recType; + if (recType == REC_TYPE_OP) { + hasOp = true; + continue; + } + if (recType == REC_TYPE_UNKNOWN) { + // these types do not advance step + continue; + } + var bumpStep = false; + var records = test[tIndex + 2]; + var fragType = records[0]; + if (recType == REC_TYPE_ADD) { + if (records.length != 2) { + console.log("expect only two elements: " + records.length); + throw "stop execution"; + } + if (fragType == ADD_MOVETO || fragType == ADD_CLOSE) { + continue; + } + ++addMax; + if (!draw_add || !inStepRange) { + continue; + } + lastAdd = tIndex; + ++addCount; + bumpStep = true; + } + if (recType == REC_TYPE_PATH && hasOp) { + secondPath = tIndex; + } + if (recType == REC_TYPE_ACTIVE) { + ++activeMax; + if (!draw_active || !inStepRange) { + continue; + } + lastActive = tIndex; + ++activeCount; + bumpStep = true; + } + if (recType == REC_TYPE_ACTIVE_OP) { + ++opMax; + if (!draw_op || !inStepRange) { + continue; + } + lastOp = tIndex; + ++opCount; + bumpStep = true; + } + if (recType == REC_TYPE_ANGLE) { + ++angleMax; + if (!draw_angle || !inStepRange) { + continue; + } + lastAngle = tIndex; + ++angleCount; + bumpStep = true; + } + if (recType == REC_TYPE_SECT) { + if (records.length != 2) { + console.log("expect only two elements: " + records.length); + throw "stop execution"; + } + ++sectMax; + var sectBump = 1; + switch (fragType) { + case INTERSECT_LINE: + case INTERSECT_QUAD_LINE: + case INTERSECT_QUAD: + case INTERSECT_SELF_CUBIC: + case INTERSECT_CUBIC_LINE: + case INTERSECT_CUBIC_QUAD: + case INTERSECT_CUBIC: + sectBump = 1; + break; + case INTERSECT_LINE_2: + case INTERSECT_QUAD_LINE_2: + case INTERSECT_QUAD_2: + case INTERSECT_CUBIC_LINE_2: + case INTERSECT_CUBIC_QUAD_2: + case INTERSECT_CUBIC_2: + sectBump = 2; + break; + case INTERSECT_LINE_NO: + case INTERSECT_QUAD_LINE_NO: + case INTERSECT_QUAD_NO: + case INTERSECT_SELF_CUBIC_NO: + case INTERSECT_CUBIC_LINE_NO: + case INTERSECT_CUBIC_QUAD_NO: + case INTERSECT_CUBIC_NO: + sectBump = 0; + break; + case INTERSECT_CUBIC_LINE_3: + case INTERSECT_CUBIC_QUAD_3: + case INTERSECT_CUBIC_3: + sectBump = 3; + break; + case INTERSECT_CUBIC_QUAD_4: + case INTERSECT_CUBIC_4: + sectBump = 4; + break; + default: + console.log("missing case " + records.length); + throw "stop execution"; + } + sectMax2 += sectBump; + if (draw_intersection <= 1 || !inStepRange) { + continue; + } + lastSect = tIndex; + sectCount += sectBump; + bumpStep = true; + } + if (recType == REC_TYPE_SORT) { + ++sortMax; + if (!draw_sort || !inStepRange) { + continue; + } + lastSort = tIndex; + ++sortCount; + bumpStep = true; + } + if (recType == REC_TYPE_MARK) { + ++markMax; + if (!draw_mark || !inStepRange) { + continue; + } + lastMark = tIndex; + ++markCount; + bumpStep = true; + } + if (bumpStep) { + lastIndex = tIndex; + logStart = test[tIndex + 1]; + logRange = records.length / 2; + ++curStep; + } + } + stepMax = (draw_add ? addMax : 0) + + (draw_active ? activeMax : 0) + + (draw_op ? opMax : 0) + + (draw_angle ? angleMax : 0) + + (draw_sort ? sortMax : 0) + + (draw_mark ? markMax : 0) + + (draw_intersection == 2 ? sectMax : draw_intersection == 3 ? sectMax2 : 0); + if (stepMax == 0) { + stepMax = addMax + activeMax + angleMax + opMax + sortMax + markMax; + } + drawnPts = []; + drawnLines = []; + drawnQuads = []; + drawnCubics = []; + focusXmin = focusYmin = Infinity; + focusXmax = focusYmax = -Infinity; + var pathIndex = 0; + var opLetter = 'S'; + for (var tIndex = lastIndex; tIndex >= 0; tIndex -= 3) { + var recType = test[tIndex]; + var records = test[tIndex + 2]; + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + if (!typeof fragType == 'number' || fragType < 1 || fragType > FRAG_TYPE_LAST) { + console.log("unknown in range frag type: " + fragType); + throw "stop execution"; + } + var frags = records[recordIndex + 1]; + focus_enabled = false; + switch (recType) { + case REC_TYPE_COMPUTED: + if (draw_computed == 0) { + continue; + } + ctx.lineWidth = 1; + ctx.strokeStyle = pathIndex == 0 ? "black" : "red"; + ctx.fillStyle = "blue"; + var drawThis = false; + switch (fragType) { + case PATH_QUAD: + if ((draw_computed & 5) == 1 || ((draw_computed & 4) != 0 + && (draw_computed & 1) == pathIndex)) { + drawQuad(frags[0], frags[1], frags[2], frags[3], + frags[4], frags[5]); + drawThis = true; + } + break; + case PATH_CUBIC: + if ((draw_computed & 6) == 2 || ((draw_computed & 4) != 0 + && (draw_computed & 1) != pathIndex)) { + drawCubic(frags[0], frags[1], frags[2], frags[3], + frags[4], frags[5], frags[6], frags[7]); + drawThis = true; + } + ++pathIndex; + break; + case COMPUTED_SET_1: + pathIndex = 0; + break; + case COMPUTED_SET_2: + pathIndex = 1; + break; + default: + console.log("unknown REC_TYPE_COMPUTED frag type: " + fragType); + throw "stop execution"; + } + if (!drawThis || collect_bounds) { + break; + } + drawCurveSpecials(test, frags, fragType); + break; + case REC_TYPE_PATH: + if (!draw_path) { + continue; + } + var firstPath = tIndex < secondPath; + if ((draw_path & (firstPath ? 1 : 2)) == 0) { + continue; + } + ctx.lineWidth = 1; + ctx.strokeStyle = firstPath ? "black" : "red"; + ctx.fillStyle = "blue"; + switch (fragType) { + case PATH_LINE: + drawLine(frags[0], frags[1], frags[2], frags[3]); + break; + case PATH_QUAD: + drawQuad(frags[0], frags[1], frags[2], frags[3], + frags[4], frags[5]); + break; + case PATH_CUBIC: + drawCubic(frags[0], frags[1], frags[2], frags[3], + frags[4], frags[5], frags[6], frags[7]); + break; + default: + console.log("unknown REC_TYPE_PATH frag type: " + fragType); + throw "stop execution"; + } + if (collect_bounds) { + break; + } + drawCurveSpecials(test, frags, fragType); + break; + case REC_TYPE_OP: + switch (fragType) { + case OP_INTERSECT: opLetter = 'I'; break; + case OP_DIFFERENCE: opLetter = 'D'; break; + case OP_UNION: opLetter = 'U'; break; + case OP_XOR: opLetter = 'X'; break; + default: + console.log("unknown REC_TYPE_OP frag type: " + fragType); + throw "stop execution"; + } + break; + case REC_TYPE_ACTIVE: + if (!draw_active || (step_limit > 0 && tIndex < lastActive)) { + continue; + } + var x1 = frags[SPAN_X1]; + var y1 = frags[SPAN_Y1]; + var x2 = frags[SPAN_X2]; + var y2 = frags[SPAN_Y2]; + var x3, y3, x3, y4, t1, t2; + ctx.lineWidth = 3; + ctx.strokeStyle = "rgba(0,0,255, 0.3)"; + focus_enabled = true; + switch (fragType) { + case ACTIVE_LINE_SPAN: + t1 = frags[SPAN_L_T]; + t2 = frags[SPAN_L_TEND]; + drawLinePartial(x1, y1, x2, y2, t1, t2); + if (draw_id) { + drawLinePartialID(frags[0], x1, y1, x2, y2, t1, t2); + } + break; + case ACTIVE_QUAD_SPAN: + x3 = frags[SPAN_X3]; + y3 = frags[SPAN_Y3]; + t1 = frags[SPAN_Q_T]; + t2 = frags[SPAN_Q_TEND]; + drawQuadPartial(x1, y1, x2, y2, x3, y3, t1, t2); + if (draw_id) { + drawQuadPartialID(frags[0], x1, y1, x2, y2, x3, y3, t1, t2); + } + break; + case ACTIVE_CUBIC_SPAN: + x3 = frags[SPAN_X3]; + y3 = frags[SPAN_Y3]; + x4 = frags[SPAN_X4]; + y4 = frags[SPAN_Y4]; + t1 = frags[SPAN_C_T]; + t2 = frags[SPAN_C_TEND]; + drawCubicPartial(x1, y1, x2, y2, x3, y3, x4, y4, t1, t2); + if (draw_id) { + drawCubicPartialID(frags[0], x1, y1, x2, y2, x3, y3, x4, y4, t1, t2); + } + break; + default: + console.log("unknown REC_TYPE_ACTIVE frag type: " + fragType); + throw "stop execution"; + } + break; + case REC_TYPE_ACTIVE_OP: + if (!draw_op || (step_limit > 0 && tIndex < lastOp)) { + continue; + } + focus_enabled = true; + ctx.lineWidth = 3; + var activeSpan = frags[7] == "1"; + ctx.strokeStyle = activeSpan ? "rgba(45,160,0, 0.3)" : "rgba(255,45,0, 0.5)"; + var curve = curvePartialByID(test, frags[0], frags[1], frags[2]); + drawCurve(curve); + if (draw_op > 1) { + drawArc(curve, false, frags[3], frags[4]); + drawArc(curve, true, frags[5], frags[6]); + } + break; + case REC_TYPE_ADD: + if (!draw_add) { + continue; + } + ctx.lineWidth = 3; + ctx.strokeStyle = closeCount == 0 ? "rgba(0,0,255, 0.3)" + : closeCount == 1 ? "rgba(0,127,0, 0.3)" + : closeCount == 2 ? "rgba(0,127,127, 0.3)" + : closeCount == 3 ? "rgba(127,127,0, 0.3)" + : "rgba(127,0,127, 0.3)"; + focus_enabled = true; + switch (fragType) { + case ADD_MOVETO: + break; + case ADD_LINETO: + if (step_limit == 0 || tIndex >= lastAdd) { + drawLine(frags[0], frags[1], frags[2], frags[3]); + } + break; + case ADD_QUADTO: + if (step_limit == 0 || tIndex >= lastAdd) { + drawQuad(frags[0], frags[1], frags[2], frags[3], frags[4], frags[5]); + } + break; + case ADD_CUBICTO: + if (step_limit == 0 || tIndex >= lastAdd) { + drawCubic(frags[0], frags[1], frags[2], frags[3], + frags[4], frags[5], frags[6], frags[7]); + } + break; + case ADD_CLOSE: + ++closeCount; + break; + case ADD_FILL: + break; + default: + console.log("unknown REC_TYPE_ADD frag type: " + fragType); + throw "stop execution"; + } + break; + case REC_TYPE_ANGLE: + if (!draw_angle || (step_limit > 0 && tIndex < lastAngle)) { + continue; + } + if (fragType != ANGLE_AFTER && fragType != ANGLE_AFTER2) { + continue; + } + focus_enabled = true; + ctx.lineWidth = 3; + ctx.strokeStyle = "rgba(127,45,127, 0.3)"; + var leftCurve, midCurve, rightCurve; + if (fragType == ANGLE_AFTER) { + leftCurve = curvePartialByID(test, frags[0], frags[3], frags[4]); + midCurve = curvePartialByID(test, frags[5], frags[8], frags[9]); + rightCurve = curvePartialByID(test, frags[10], frags[13], frags[14]); + } else { + leftCurve = curvePartialByID(test, frags[0], frags[4], frags[5]); + midCurve = curvePartialByID(test, frags[6], frags[10], frags[11]); + rightCurve = curvePartialByID(test, frags[12], frags[16], frags[17]); + } + drawCurve(leftCurve); + drawCurve(rightCurve); + var inBetween = frags[fragType == ANGLE_AFTER ? 15 : 18] == "T"; + ctx.strokeStyle = inBetween ? "rgba(0,160,45, 0.3)" : "rgba(255,0,45, 0.5)"; + drawCurve(midCurve); + if (draw_angle > 1) { + drawOrder(leftCurve, 'L'); + drawOrder(rightCurve, 'R'); + } + break; + case REC_TYPE_SECT: + if (!draw_intersection) { + continue; + } + if (draw_intersection != 1 && (step_limit > 0 && tIndex < lastSect)) { + continue; + } + // draw_intersection == 1 : show all + // draw_intersection == 2 : step == 0 ? show all : show intersection line #step + // draw_intersection == 3 : step == 0 ? show all : show intersection #step + ctx.lineWidth = 1; + ctx.strokeStyle = "rgba(0,0,255, 0.3)"; + ctx.fillStyle = "blue"; + focus_enabled = true; + var f = []; + var c1s; + var c1l; + var c2s; + var c2l; + switch (fragType) { + case INTERSECT_LINE: + f.push(5, 6, 0, 7); + c1s = 1; c1l = 4; c2s = 8; c2l = 4; + break; + case INTERSECT_LINE_2: + f.push(5, 6, 0, 10); + f.push(8, 9, 7, 15); + c1s = 1; c1l = 4; c2s = 11; c2l = 4; + break; + case INTERSECT_LINE_NO: + c1s = 0; c1l = 4; c2s = 4; c2l = 4; + break; + case INTERSECT_QUAD_LINE: + f.push(7, 8, 0, 9); + c1s = 1; c1l = 6; c2s = 10; c2l = 4; + break; + case INTERSECT_QUAD_LINE_2: + f.push(7, 8, 0, 12); + f.push(10, 11, 9, 17); + c1s = 1; c1l = 6; c2s = 13; c2l = 4; + break; + case INTERSECT_QUAD_LINE_NO: + c1s = 0; c1l = 6; c2s = 6; c2l = 4; + break; + case INTERSECT_QUAD: + f.push(7, 8, 0, 9); + c1s = 1; c1l = 6; c2s = 10; c2l = 6; + break; + case INTERSECT_QUAD_2: + f.push(7, 8, 0, 12); + f.push(10, 11, 9, 19); + c1s = 1; c1l = 6; c2s = 13; c2l = 6; + break; + case INTERSECT_QUAD_NO: + c1s = 0; c1l = 6; c2s = 6; c2l = 6; + break; + case INTERSECT_SELF_CUBIC: + f.push(9, 10, 0, 11); + c1s = 1; c1l = 8; c2s = 0; c2l = 0; + break; + case INTERSECT_SELF_CUBIC_NO: + c1s = 0; c1l = 8; c2s = 0; c2l = 0; + break; + case INTERSECT_CUBIC_LINE: + f.push(9, 10, 0, 11); + c1s = 1; c1l = 8; c2s = 12; c2l = 4; + break; + case INTERSECT_CUBIC_LINE_2: + f.push(9, 10, 0, 14); + f.push(12, 13, 11, 19); + c1s = 1; c1l = 8; c2s = 15; c2l = 4; + break; + case INTERSECT_CUBIC_LINE_3: + f.push(9, 10, 0, 17); + f.push(12, 13, 11, 22); + f.push(15, 16, 14, 23); + c1s = 1; c1l = 8; c2s = 18; c2l = 4; + break; + case INTERSECT_CUBIC_QUAD_NO: + c1s = 0; c1l = 8; c2s = 8; c2l = 6; + break; + case INTERSECT_CUBIC_QUAD: + f.push(9, 10, 0, 11); + c1s = 1; c1l = 8; c2s = 12; c2l = 6; + break; + case INTERSECT_CUBIC_QUAD_2: + f.push(9, 10, 0, 14); + f.push(12, 13, 11, 21); + c1s = 1; c1l = 8; c2s = 15; c2l = 6; + break; + case INTERSECT_CUBIC_QUAD_3: + f.push(9, 10, 0, 17); + f.push(12, 13, 11, 24); + f.push(15, 16, 14, 25); + c1s = 1; c1l = 8; c2s = 18; c2l = 6; + break; + case INTERSECT_CUBIC_QUAD_4: + f.push(9, 10, 0, 20); + f.push(12, 13, 11, 27); + f.push(15, 16, 14, 28); + f.push(18, 19, 17, 29); + c1s = 1; c1l = 8; c2s = 21; c2l = 6; + break; + case INTERSECT_CUBIC_LINE_NO: + c1s = 0; c1l = 8; c2s = 8; c2l = 4; + break; + case INTERSECT_CUBIC: + f.push(9, 10, 0, 11); + c1s = 1; c1l = 8; c2s = 12; c2l = 8; + break; + case INTERSECT_CUBIC_2: + f.push(9, 10, 0, 14); + f.push(12, 13, 11, 23); + c1s = 1; c1l = 8; c2s = 15; c2l = 8; + break; + case INTERSECT_CUBIC_3: + f.push(9, 10, 0, 17); + f.push(12, 13, 11, 26); + f.push(15, 16, 14, 27); + c1s = 1; c1l = 8; c2s = 18; c2l = 8; + break; + case INTERSECT_CUBIC_4: + f.push(9, 10, 0, 20); + f.push(12, 13, 11, 29); + f.push(15, 16, 14, 30); + f.push(18, 19, 17, 31); + c1s = 1; c1l = 8; c2s = 21; c2l = 8; + break; + case INTERSECT_CUBIC_NO: + c1s = 0; c1l = 8; c2s = 8; c2l = 8; + break; + default: + console.log("unknown REC_TYPE_SECT frag type: " + fragType); + throw "stop execution"; + } + if (draw_intersection != 1) { + var id = -1; + var curve; + switch (c1l) { + case 4: + drawLine(frags[c1s], frags[c1s + 1], frags[c1s + 2], frags[c1s + 3]); + if (draw_id) { + curve = [frags[c1s], frags[c1s + 1], frags[c1s + 2], frags[c1s + 3]]; + id = idByCurve(test, curve, PATH_LINE); + } + break; + case 6: + drawQuad(frags[c1s], frags[c1s + 1], frags[c1s + 2], frags[c1s + 3], + frags[c1s + 4], frags[c1s + 5]); + if (draw_id) { + curve = [frags[c1s], frags[c1s + 1], frags[c1s + 2], frags[c1s + 3], + frags[c1s + 4], frags[c1s + 5]]; + id = idByCurve(test, curve, PATH_QUAD); + } + break; + case 8: + drawCubic(frags[c1s], frags[c1s + 1], frags[c1s + 2], frags[c1s + 3], + frags[c1s + 4], frags[c1s + 5], frags[c1s + 6], frags[c1s + 7]); + if (draw_id) { + curve = [frags[c1s], frags[c1s + 1], frags[c1s + 2], frags[c1s + 3], + frags[c1s + 4], frags[c1s + 5], frags[c1s + 6], frags[c1s + 7]]; + id = idByCurve(test, curve, PATH_CUBIC); + } + break; + } + if (id >= 0) { + drawID(curve, id); + } + id = -1; + switch (c2l) { + case 0: + break; + case 4: + drawLine(frags[c2s], frags[c2s + 1], frags[c2s + 2], frags[c2s + 3]); + if (draw_id) { + curve = [frags[c2s], frags[c2s + 1], frags[c2s + 2], frags[c2s + 3]]; + id = idByCurve(test, curve, PATH_LINE); + } + break; + case 6: + drawQuad(frags[c2s], frags[c2s + 1], frags[c2s + 2], frags[c2s + 3], + frags[c2s + 4], frags[c2s + 5]); + if (draw_id) { + curve = [frags[c2s], frags[c2s + 1], frags[c2s + 2], frags[c2s + 3], + frags[c2s + 4], frags[c2s + 5]]; + id = idByCurve(test, curve, PATH_QUAD); + } + break; + case 8: + drawCubic(frags[c2s], frags[c2s + 1], frags[c2s + 2], frags[c2s + 3], + frags[c2s + 4], frags[c2s + 5], frags[c2s + 6], frags[c2s + 7]); + if (draw_id) { + curve = [frags[c2s], frags[c2s + 1], frags[c2s + 2], frags[c2s + 3], + frags[c2s + 4], frags[c2s + 5], frags[c2s + 6], frags[c2s + 7]]; + id = idByCurve(test, curve, PATH_CUBIC); + } + break; + } + if (id >= 0) { + drawID(curve, id); + } + } + if (collect_bounds) { + break; + } + for (var idx = 0; idx < f.length; idx += 4) { + if (draw_intersection != 3 || idx == lastSect - tIndex) { + drawPoint(frags[f[idx]], frags[f[idx + 1]], true); + } + } + if (!draw_intersectT) { + break; + } + ctx.fillStyle = "red"; + for (var idx = 0; idx < f.length; idx += 4) { + if (draw_intersection != 3 || idx == lastSect - tIndex) { + drawTAtPointUp(frags[f[idx]], frags[f[idx + 1]], frags[f[idx + 2]]); + drawTAtPointDown(frags[f[idx]], frags[f[idx + 1]], frags[f[idx + 3]]); + } + } + break; + case REC_TYPE_SORT: + if (!draw_sort || (step_limit > 0 && tIndex < lastSort)) { + continue; + } + ctx.lineWidth = 3; + ctx.strokeStyle = "rgba(127,127,0, 0.5)"; + focus_enabled = true; + switch (fragType) { + case SORT_UNARY: + case SORT_BINARY: + var curve = curvePartialByID(test, frags[0], frags[6], frags[8]); + drawCurve(curve); + break; + default: + console.log("unknown REC_TYPE_SORT frag type: " + fragType); + throw "stop execution"; + } + break; + case REC_TYPE_MARK: + if (!draw_mark || (step_limit > 0 && tIndex < lastMark)) { + continue; + } + ctx.lineWidth = 3; + ctx.strokeStyle = fragType >= MARK_DONE_LINE ? + "rgba(127,0,127, 0.5)" : "rgba(127,127,0, 0.5)"; + focus_enabled = true; + switch (fragType) { + case MARK_LINE: + case MARK_DONE_LINE: + case MARK_UNSORTABLE_LINE: + case MARK_SIMPLE_LINE: + case MARK_SIMPLE_DONE_LINE: + case MARK_DONE_UNARY_LINE: + drawLinePartial(frags[1], frags[2], frags[3], frags[4], + frags[5], frags[9]); + if (draw_id) { + drawLinePartialID(frags[0], frags[1], frags[2], frags[3], frags[4], + frags[5], frags[9]); + } + break; + case MARK_QUAD: + case MARK_DONE_QUAD: + case MARK_UNSORTABLE_QUAD: + case MARK_SIMPLE_QUAD: + case MARK_SIMPLE_DONE_QUAD: + case MARK_DONE_UNARY_QUAD: + drawQuadPartial(frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6], frags[7], frags[11]); + if (draw_id) { + drawQuadPartialID(frags[0], frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6], frags[7], frags[11]); + } + break; + case MARK_CUBIC: + case MARK_DONE_CUBIC: + case MARK_UNSORTABLE_CUBIC: + case MARK_SIMPLE_CUBIC: + case MARK_SIMPLE_DONE_CUBIC: + case MARK_DONE_UNARY_CUBIC: + drawCubicPartial(frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6], frags[7], frags[8], frags[9], frags[13]); + if (draw_id) { + drawCubicPartialID(frags[0], frags[1], frags[2], frags[3], frags[4], + frags[5], frags[6], frags[7], frags[8], frags[9], frags[13]); + } + break; + case MARK_ANGLE_LAST: + // FIXME: ignored for now + break; + default: + console.log("unknown REC_TYPE_MARK frag type: " + fragType); + throw "stop execution"; + } + break; + default: + continue; + } + } + switch (recType) { + case REC_TYPE_SORT: + if (!draw_sort || (step_limit > 0 && tIndex < lastSort)) { + break; + } + var angles = []; // use tangent lines to describe arcs + var windFrom = []; + var windTo = []; + var opp = []; + var minXY = Number.MAX_VALUE; + var partial; + focus_enabled = true; + var someUnsortable = false; + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + var frags = records[recordIndex + 1]; + var unsortable = (fragType == SORT_UNARY && frags[14]) || + (fragType == SORT_BINARY && frags[16]); + someUnsortable |= unsortable; + switch (fragType) { + case SORT_UNARY: + case SORT_BINARY: + partial = curvePartialByID(test, frags[0], frags[6], frags[8]); + break; + default: + console.log("unknown REC_TYPE_SORT frag type: " + fragType); + throw "stop execution"; + } + var dx = boundsWidth(partial); + var dy = boundsHeight(partial); + minXY = Math.min(minXY, dx * dx + dy * dy); + if (collect_bounds) { + continue; + } + angles.push(tangent(partial)); + var from = frags[12]; + var to = frags[12]; + var sgn = frags[10]; + if (sgn < 0) { + from -= frags[11]; + } else if (sgn > 0) { + to -= frags[11]; + } + windFrom.push(from + (unsortable ? "!" : "")); + windTo.push(to + (unsortable ? "!" : "")); + opp.push(fragType == SORT_BINARY); + if (draw_sort == 1) { + drawOrder(partial, frags[12]); + } else { + drawOrder(partial, (recordIndex / 2) + 1); + } + } + var radius = Math.sqrt(minXY) / 2 * scale; + radius = Math.min(50, radius); + var scaledRadius = radius / scale; + var centerX = partial[0]; + var centerY = partial[1]; + if (collect_bounds) { + if (focus_enabled) { + focusXmin = Math.min(focusXmin, centerX - scaledRadius); + focusYmin = Math.min(focusYmin, centerY - scaledRadius); + focusXmax = Math.max(focusXmax, centerX + scaledRadius); + focusYmax = Math.max(focusYmax, centerY + scaledRadius); + } + break; + } + break; + default: + break; + } + } + if (collect_bounds) { + return; + } + if (draw_log && logStart >= 0) { + ctx.font = "normal 10px Arial"; + ctx.textAlign = "left"; + ctx.beginPath(); + var top = screenHeight - 20 - (logRange + 2) * 10; + ctx.rect(50, top, screenWidth - 100, (logRange + 2) * 10); + ctx.fillStyle = "white"; + ctx.fill(); + ctx.fillStyle = "rgba(0,0,0, 0.5)"; + if (logStart > 0) { + ctx.fillText(lines[logStart - 1], 50, top + 8); + } + ctx.fillStyle = "black"; + for (var idx = 0; idx < logRange; ++idx) { + ctx.fillText(lines[logStart + idx], 50, top + 18 + 10 * idx); + } + ctx.fillStyle = "rgba(0,0,0, 0.5)"; + if (logStart + logRange < lines.length) { + ctx.fillText(lines[logStart + logRange], 50, top + 18 + 10 * logRange); + } + } + if (draw_legend) { + var pos = 0; + var drawSomething = draw_add | draw_active | draw_sort | draw_mark; + // drawBox(pos++, "yellow", "black", opLetter, true, ''); + drawBox(pos++, "rgba(0,0,255, 0.3)", "black", draw_intersection > 1 ? sectCount : sectMax2, draw_intersection, intersectionKey); + drawBox(pos++, "rgba(0,0,255, 0.3)", "black", draw_add ? addCount : addMax, draw_add, addKey); + drawBox(pos++, "rgba(0,0,255, 0.3)", "black", draw_active ? activeCount : activeMax, draw_active, activeKey); + drawBox(pos++, "rgba(127,127,0, 0.3)", "black", draw_angle ? angleCount : angleMax, draw_angle, angleKey); + drawBox(pos++, "rgba(127,127,0, 0.3)", "black", draw_op ? opCount : opMax, draw_op, opKey); + drawBox(pos++, "rgba(127,127,0, 0.3)", "black", draw_sort ? sortCount : sortMax, draw_sort, sortKey); + drawBox(pos++, "rgba(127,0,127, 0.3)", "black", draw_mark ? markCount : markMax, draw_mark, markKey); + drawBox(pos++, "black", "white", + (new Array('P', 'P1', 'P2', 'P'))[draw_path], draw_path != 0, pathKey); + drawBox(pos++, "rgba(0,63,0, 0.7)", "white", + (new Array('Q', 'Q', 'C', 'QC', 'Qc', 'Cq'))[draw_computed], + draw_computed != 0, computedKey); + drawBox(pos++, "green", "black", step_limit, drawSomething, ''); + drawBox(pos++, "green", "black", stepMax, drawSomething, ''); + drawBox(pos++, "rgba(255,0,0, 0.6)", "black", lastIndex, drawSomething & draw_log, ''); + drawBox(pos++, "rgba(255,0,0, 0.6)", "black", test.length - 1, drawSomething & draw_log, ''); + if (curve_t) { + drawCurveTControl(); + } + ctx.font = "normal 20px Arial"; + ctx.fillStyle = "rgba(0,0,0, 0.3)"; + ctx.textAlign = "right"; + ctx.fillText(scale.toFixed(decimal_places) + 'x' , screenWidth - 10, screenHeight - 5); + } + if (draw_hints) { + ctx.font = "normal 10px Arial"; + ctx.fillStyle = "rgba(0,0,0, 0.5)"; + ctx.textAlign = "right"; + var y = 4; + ctx.fillText("control lines : " + controlLinesKey, ctx.screenWidthwidth - 10, pos * 50 + y++ * 10); + ctx.fillText("curve t : " + curveTKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("deriviatives : " + deriviativesKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("intersect t : " + intersectTKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("hodo : " + hodoKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("log : " + logKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("log curve : " + logCurvesKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("mid point : " + midpointKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("points : " + ptsKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("sequence : " + sequenceKey, screenWidth - 10, pos * 50 + y++ * 10); + ctx.fillText("xy : " + xyKey, screenWidth - 10, pos * 50 + y++ * 10); + } +} + +function drawBox(y, backC, foreC, str, enable, label) { + ctx.beginPath(); + ctx.fillStyle = backC; + ctx.rect(screenWidth - 40, y * 50 + 10, 40, 30); + ctx.fill(); + ctx.font = "normal 16px Arial"; + ctx.fillStyle = foreC; + ctx.textAlign = "center"; + ctx.fillText(str, screenWidth - 20, y * 50 + 32); + if (!enable) { + ctx.fillStyle = "rgba(255,255,255, 0.5)"; + ctx.fill(); + } + if (label != '') { + ctx.font = "normal 9px Arial"; + ctx.fillStyle = "black"; + ctx.fillText(label, screenWidth - 47, y * 50 + 40); + } +} + +function drawCurveTControl() { + ctx.lineWidth = 2; + ctx.strokeStyle = "rgba(0,0,0, 0.3)"; + ctx.beginPath(); + ctx.rect(screenWidth - 80, 40, 28, screenHeight - 80); + ctx.stroke(); + var ty = 40 + curveT * (screenHeight - 80); + ctx.beginPath(); + ctx.moveTo(screenWidth - 80, ty); + ctx.lineTo(screenWidth - 85, ty - 5); + ctx.lineTo(screenWidth - 85, ty + 5); + ctx.lineTo(screenWidth - 80, ty); + ctx.fillStyle = "rgba(0,0,0, 0.6)"; + ctx.fill(); + var num = curveT.toFixed(decimal_places); + ctx.font = "normal 10px Arial"; + ctx.textAlign = "left"; + ctx.fillText(num, screenWidth - 78, ty); +} + +function ptInTControl() { + var e = window.event; + var tgt = e.target || e.srcElement; + var left = tgt.offsetLeft; + var top = tgt.offsetTop; + var x = (e.clientX - left); + var y = (e.clientY - top); + if (x < screenWidth - 80 || x > screenWidth - 50) { + return false; + } + if (y < 40 || y > screenHeight - 80) { + return false; + } + curveT = (y - 40) / (screenHeight - 120); + if (curveT < 0 || curveT > 1) { + throw "stop execution"; + } + return true; +} + +function drawTop() { + if (tests[testIndex] == null) { + var str = testDivs[testIndex].textContent; + parse_all(str); + var title = testDivs[testIndex].id.toString(); + testTitles[testIndex] = title; + } + init(tests[testIndex]); + redraw(); +} + +function redraw() { + if (focus_on_selection) { + collect_bounds = true; + draw(tests[testIndex], testLines[testIndex], testTitles[testIndex]); + collect_bounds = false; + if (focusXmin < focusXmax && focusYmin < focusYmax) { + setScale(focusXmin, focusXmax, focusYmin, focusYmax); + } + } + ctx.beginPath(); + ctx.fillStyle = "white"; + ctx.rect(0, 0, screenWidth, screenHeight); + ctx.fill(); + draw(tests[testIndex], testLines[testIndex], testTitles[testIndex]); +} + +function dumpCurvePartial(test, id, t0, t1) { + var curve = curveByID(test, id); + var name = ["line", "quad", "cubic"][curve.length / 2 - 2]; + console.log("id=" + id + " " + name + "=" + curveToString(curve) + + " t0=" + t0 + " t1=" + t1 + + " partial=" + curveToString(curvePartialByID(test, id, t0, t1))); +} + +function dumpAngleTest(test, id, t0, t1) { + var curve = curveByID(test, id); + console.log(" { {" + curveToString(curve) + "}, " + + curve.length / 2 + ", " + t0 + ", " + t1 + ", {} }, //"); +} + +function dumpLogToConsole() { + if (logStart < 0) { + return; + } + var test = tests[testIndex]; + var recType = REC_TYPE_UNKNOWN; + var records; + for (var index = 0; index < test.length; index += 3) { + var lastLineNo = test[index + 1]; + if (lastLineNo >= logStart && lastLineNo < logStart + logRange) { + recType = test[index]; + records = test[index + 2]; + break; + } + } + if (recType == REC_TYPE_UNKNOWN) { + return; + } + var lines = testLines[testIndex]; + for (var idx = 0; idx < logRange; ++idx) { + var line = lines[logStart + idx]; + console.log(line); + for (var recordIndex = 0; recordIndex < records.length; recordIndex += 2) { + var fragType = records[recordIndex]; + var frags = records[recordIndex + 1]; + if (recType == REC_TYPE_ANGLE && fragType == ANGLE_AFTER) { + dumpCurvePartial(test, frags[0], frags[3], frags[4]); + dumpCurvePartial(test, frags[5], frags[8], frags[9]); + dumpCurvePartial(test, frags[10], frags[13], frags[14]); + console.log("\nstatic IntersectData intersectDataSet[] = {"); + dumpAngleTest(test, frags[0], frags[3], frags[4]); + dumpAngleTest(test, frags[5], frags[8], frags[9]); + dumpAngleTest(test, frags[10], frags[13], frags[14]); + console.log("};"); + } else if (recType == REC_TYPE_ANGLE && fragType == ANGLE_AFTER2) { + dumpCurvePartial(test, frags[0], frags[4], frags[5]); + dumpCurvePartial(test, frags[6], frags[10], frags[11]); + dumpCurvePartial(test, frags[12], frags[16], frags[17]); + console.log("\nstatic IntersectData intersectDataSet[] = { //"); + dumpAngleTest(test, frags[0], frags[4], frags[5]); + dumpAngleTest(test, frags[6], frags[10], frags[11]); + dumpAngleTest(test, frags[12], frags[16], frags[17]); + console.log("}; //"); + } + } + } +} + +var activeKey = 'a'; +var pathKey = 'b'; +var pathBackKey = 'B'; +var centerKey = 'c'; +var addKey = 'd'; +var deriviativesKey = 'f'; +var angleKey = 'g'; +var angleBackKey = 'G'; +var hodoKey = 'h'; +var intersectionKey = 'i'; +var intersectionBackKey = 'I'; +var sequenceKey = 'j'; +var midpointKey = 'k'; +var logKey = 'l'; +var logToConsoleKey = 'L'; +var markKey = 'm'; +var sortKey = 'o'; +var opKey = 'p'; +var opBackKey = 'P'; +var computedKey = 'q'; +var computedBackKey = 'Q'; +var stepKey = 's'; +var stepBackKey = 'S'; +var intersectTKey = 't'; +var curveTKey = 'u'; +var controlLinesBackKey = 'V'; +var controlLinesKey = 'v'; +var ptsKey = 'x'; +var xyKey = 'y'; +var logCurvesKey = 'z'; +var focusKey = '`'; +var idKey = '.'; +var retinaKey = '\\'; + +function doKeyPress(evt) { + var char = String.fromCharCode(evt.charCode); + var focusWasOn = false; + switch (char) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + decimal_places = char - '0'; + redraw(); + break; + case activeKey: + draw_active ^= true; + redraw(); + break; + case addKey: + draw_add ^= true; + redraw(); + break; + case angleKey: + draw_angle = (draw_angle + 1) % 3; + redraw(); + break; + case angleBackKey: + draw_angle = (draw_angle + 2) % 3; + redraw(); + break; + case centerKey: + setScale(xmin, xmax, ymin, ymax); + redraw(); + break; + case controlLinesBackKey: + control_lines = (control_lines + 3) % 4; + redraw(); + break; + case controlLinesKey: + control_lines = (control_lines + 1) % 4; + redraw(); + break; + case computedBackKey: + draw_computed = (draw_computed + 5) % 6; + redraw(); + break; + case computedKey: + draw_computed = (draw_computed + 1) % 6; + redraw(); + break; + case curveTKey: + curve_t ^= true; + if (curve_t) { + draw_legend = true; + } + redraw(); + break; + case deriviativesKey: + draw_deriviatives = (draw_deriviatives + 1) % 3; + redraw(); + break; + case focusKey: + focus_on_selection ^= true; + setScale(xmin, xmax, ymin, ymax); + redraw(); + break; + case hodoKey: + draw_hodo = (draw_hodo + 1) % 4; + redraw(); + break; + case idKey: + draw_id ^= true; + redraw(); + break; + case intersectionBackKey: + draw_intersection = (draw_intersection + 3) % 4; + redraw(); + break; + case intersectionKey: + draw_intersection = (draw_intersection + 1) % 4; + redraw(); + break; + case intersectTKey: + draw_intersectT ^= true; + redraw(); + break; + case logCurvesKey: + logCurves(tests[testIndex]); + break; + case logKey: + draw_log ^= true; + redraw(); + break; + case logToConsoleKey: + if (draw_log) { + dumpLogToConsole(); + } + break; + case markKey: + draw_mark ^= true; + redraw(); + break; + case midpointKey: + draw_midpoint ^= true; + redraw(); + break; + case opKey: + draw_op = (draw_op + 1) % 3; + redraw(); + break; + case opBackKey: + draw_op = (draw_op + 2) % 3; + redraw(); + break; + case pathKey: + draw_path = (draw_path + 1) % 4; + redraw(); + break; + case pathBackKey: + draw_path = (draw_path + 3) % 4; + redraw(); + break; + case ptsKey: + pt_labels = (pt_labels + 1) % 3; + redraw(); + break; + case retinaKey: + retina_scale ^= true; + drawTop(); + break; + case sequenceKey: + draw_sequence ^= true; + redraw(); + break; + case sortKey: + draw_sort = (draw_sort + 1) % 3; + drawTop(); + break; + case stepKey: + step_limit++; + if (step_limit > stepMax) { + step_limit = stepMax; + } + redraw(); + break; + case stepBackKey: + step_limit--; + if (step_limit < 0) { + step_limit = 0; + } + redraw(); + break; + case xyKey: + debug_xy = (debug_xy + 1) % 3; + redraw(); + break; + case '-': + focusWasOn = focus_on_selection; + if (focusWasOn) { + focus_on_selection = false; + scale /= 1.2; + } else { + scale /= 2; + calcLeftTop(); + } + redraw(); + focus_on_selection = focusWasOn; + break; + case '=': + case '+': + focusWasOn = focus_on_selection; + if (focusWasOn) { + focus_on_selection = false; + scale *= 1.2; + } else { + scale *= 2; + calcLeftTop(); + } + redraw(); + focus_on_selection = focusWasOn; + break; + case '?': + draw_hints ^= true; + if (draw_hints && !draw_legend) { + draw_legend = true; + } + redraw(); + break; + case '/': + draw_legend ^= true; + redraw(); + break; + } +} + +function doKeyDown(evt) { + var char = evt.keyCode; + var preventDefault = false; + switch (char) { + case 37: // left arrow + if (evt.shiftKey) { + testIndex -= 9; + } + if (--testIndex < 0) + testIndex = tests.length - 1; + drawTop(); + preventDefault = true; + break; + case 39: // right arrow + if (evt.shiftKey) { + testIndex += 9; + } + if (++testIndex >= tests.length) + testIndex = 0; + drawTop(); + preventDefault = true; + break; + } + if (preventDefault) { + evt.preventDefault(); + return false; + } + return true; +} + +(function() { + var hidden = "hidden"; + + // Standards: + if (hidden in document) + document.addEventListener("visibilitychange", onchange); + else if ((hidden = "mozHidden") in document) + document.addEventListener("mozvisibilitychange", onchange); + else if ((hidden = "webkitHidden") in document) + document.addEventListener("webkitvisibilitychange", onchange); + else if ((hidden = "msHidden") in document) + document.addEventListener("msvisibilitychange", onchange); + // IE 9 and lower: + else if ('onfocusin' in document) + document.onfocusin = document.onfocusout = onchange; + // All others: + else + window.onpageshow = window.onpagehide + = window.onfocus = window.onblur = onchange; + + function onchange (evt) { + var v = 'visible', h = 'hidden', + evtMap = { + focus:v, focusin:v, pageshow:v, blur:h, focusout:h, pagehide:h + }; + + evt = evt || window.event; + if (evt.type in evtMap) + document.body.className = evtMap[evt.type]; + else + document.body.className = this[hidden] ? "hidden" : "visible"; + } +})(); + +function calcXY() { + var e = window.event; + var tgt = e.target || e.srcElement; + var left = tgt.offsetLeft; + var top = tgt.offsetTop; + mouseX = (e.clientX - left) / scale + srcLeft; + mouseY = (e.clientY - top) / scale + srcTop; +} + +function calcLeftTop() { + srcLeft = mouseX - screenWidth / 2 / scale; + srcTop = mouseY - screenHeight / 2 / scale; +} + +var disableClick = false; + +function handleMouseClick() { + if (disableClick) { + return; + } + if (!curve_t || !ptInTControl()) { + calcXY(); + calcLeftTop(); + } + redraw(); +// if (!curve_t || !ptInTControl()) { +// mouseX = screenWidth / 2 / scale + srcLeft; +// mouseY = screenHeight / 2 / scale + srcTop; +// } +} + +function handleMouseOver() { + calcXY(); + if (debug_xy != 2) { + return; + } + var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places); + ctx.beginPath(); + ctx.rect(300,100,num.length * 6,10); + ctx.fillStyle="white"; + ctx.fill(); + ctx.font = "normal 10px Arial"; + ctx.fillStyle="black"; + ctx.textAlign = "left"; + ctx.fillText(num, 300, 108); +} + +function start() { + for (var i = 0; i < testDivs.length; ++i) { + tests[i] = null; + } + testIndex = 0; + drawTop(); + window.addEventListener('keypress', doKeyPress, true); + window.addEventListener('keydown', doKeyDown, true); + window.onresize = function() { + drawTop(); + } + /* + window.onpagehide = function() { + disableClick = true; + } + */ + window.onpageshow = function () { + disableClick = false; + } +} + +</script> +</head> + +<body onLoad="start();"> +<canvas id="canvas" width="750" height="500" + onmousemove="handleMouseOver()" + onclick="handleMouseClick()" + ></canvas > +</body> +</html> |