diff options
author | caryclark@google.com <caryclark@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2012-07-02 20:27:02 +0000 |
---|---|---|
committer | caryclark@google.com <caryclark@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2012-07-02 20:27:02 +0000 |
commit | 8dcf114db9762c02d217beba6e29dffa4e92d298 (patch) | |
tree | 3d55d0135db941b88684a11cd794498b1777fb94 /experimental/Intersection/Simplify.cpp | |
parent | 0985c558fc7e014f5680a7532270c6cbad2bca6a (diff) |
shape ops work in progress
M Intersection/DataTypes.cpp
M Intersection/QuadraticIntersection_Test.cpp
M Intersection/EdgeWalker.cpp
M Intersection/LineQuadraticIntersection_Test.cpp
M Intersection/LineIntersection_Test.cpp
M Intersection/LineIntersection.cpp
D Intersection/edge.xcodeproj
M Intersection/SimplifyFindTop_Test.cpp
M Intersection/DataTypes.h
A Intersection/SimplifyRect4x4_Test.cpp
M Intersection/CubicIntersection_Test.cpp
M Intersection/QuadraticUtilities.h
M Intersection/LineCubicIntersection_Test.cpp
A Intersection/CurveUtilities.h
M Intersection/QuadraticBezierClip.cpp
M Intersection/QuadraticBounds.cpp
M Intersection/LineUtilities.h
M Intersection/Intersection_Tests.cpp
M Intersection/Simplify.cpp
M Intersection/EdgeWalker_TestUtility.cpp
M Intersection/QuadraticUtilities.cpp
M Intersection/thingsToDo.txt
M Intersection/LineUtilities.cpp
M Intersection/CubicUtilities.h
M Intersection/SimplifyFindNext_Test.cpp
M Intersection/Intersection_Tests.h
M Intersection/CubicBezierClip.cpp
M Intersection/ActiveEdge_Test.cpp
M Intersection/CubicBounds.cpp
M Intersection/Simplify.h
M Intersection/SimplifyNew_Test.cpp
M Intersection/EdgeWalker_Test.h
M Intersection/CubicUtilities.cpp
M Intersection/op.htm
M Intersection/ConvexHull.cpp
D Intersection/RectUtilities.cpp
M Intersection/SimplifyAddIntersectingTs_Test.cpp
git-svn-id: http://skia.googlecode.com/svn/trunk@4429 2bbb7eff-a529-9590-31e7-b0007b416f81
Diffstat (limited to 'experimental/Intersection/Simplify.cpp')
-rw-r--r-- | experimental/Intersection/Simplify.cpp | 1391 |
1 files changed, 954 insertions, 437 deletions
diff --git a/experimental/Intersection/Simplify.cpp b/experimental/Intersection/Simplify.cpp index b57acf341d..23a0a3dc62 100644 --- a/experimental/Intersection/Simplify.cpp +++ b/experimental/Intersection/Simplify.cpp @@ -25,9 +25,12 @@ #define DEBUG_ADD_INTERSECTING_TS 0 #define DEBUG_BRIDGE 0 +#define DEBUG_CROSS 0 #define DEBUG_DUMP 0 #define DEBUG_PATH_CONSTRUCTION 0 +#define DEBUG_WINDING 0 #define DEBUG_UNUSED 0 // set to expose unused functions +#define DEBUG_MARK_DONE 0 #else @@ -35,9 +38,12 @@ #define DEBUG_ADD_INTERSECTING_TS 0 #define DEBUG_BRIDGE 1 +#define DEBUG_CROSS 1 #define DEBUG_DUMP 1 #define DEBUG_PATH_CONSTRUCTION 1 +#define DEBUG_WINDING 0 #define DEBUG_UNUSED 0 // set to expose unused functions +#define DEBUG_MARK_DONE 01 #endif @@ -48,6 +54,10 @@ static int gContourID; static int gSegmentID; #endif +#ifndef DEBUG_TEST +#define DEBUG_TEST 0 +#endif + static int LineIntersect(const SkPoint a[2], const SkPoint b[2], Intersections& intersections) { const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}}; @@ -95,24 +105,12 @@ static int HLineIntersect(const SkPoint a[2], SkScalar left, SkScalar right, return horizontalIntersect(aLine, left, right, y, flipped, intersections); } -static int VLineIntersect(const SkPoint a[2], SkScalar left, SkScalar right, - SkScalar y, bool flipped, Intersections& intersections) { - const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}}; - return verticalIntersect(aLine, left, right, y, flipped, intersections); -} - static int HQuadIntersect(const SkPoint a[3], SkScalar left, SkScalar right, SkScalar y, bool flipped, Intersections& intersections) { const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}}; return horizontalIntersect(aQuad, left, right, y, flipped, intersections); } -static int VQuadIntersect(const SkPoint a[3], SkScalar left, SkScalar right, - SkScalar y, bool flipped, Intersections& intersections) { - const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}}; - return verticalIntersect(aQuad, left, right, y, flipped, intersections); -} - static int HCubicIntersect(const SkPoint a[4], SkScalar left, SkScalar right, SkScalar y, bool flipped, Intersections& intersections) { const Cubic aCubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}, @@ -120,13 +118,33 @@ static int HCubicIntersect(const SkPoint a[4], SkScalar left, SkScalar right, return horizontalIntersect(aCubic, left, right, y, flipped, intersections); } -static int VCubicIntersect(const SkPoint a[4], SkScalar left, SkScalar right, - SkScalar y, bool flipped, Intersections& intersections) { +static int VLineIntersect(const SkPoint a[2], SkScalar top, SkScalar bottom, + SkScalar x, bool flipped, Intersections& intersections) { + const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}}; + return verticalIntersect(aLine, top, bottom, x, flipped, intersections); +} + +static int VQuadIntersect(const SkPoint a[3], SkScalar top, SkScalar bottom, + SkScalar x, bool flipped, Intersections& intersections) { + const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}}; + return verticalIntersect(aQuad, top, bottom, x, flipped, intersections); +} + +static int VCubicIntersect(const SkPoint a[4], SkScalar top, SkScalar bottom, + SkScalar x, bool flipped, Intersections& intersections) { const Cubic aCubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}, {a[3].fX, a[3].fY}}; - return verticalIntersect(aCubic, left, right, y, flipped, intersections); + return verticalIntersect(aCubic, top, bottom, x, flipped, intersections); } +static int (* const VSegmentIntersect[])(const SkPoint [], SkScalar , + SkScalar , SkScalar , bool , Intersections& ) = { + NULL, + VLineIntersect, + VQuadIntersect, + VCubicIntersect +}; + static void LineXYAtT(const SkPoint a[2], double t, SkPoint* out) { const _Line line = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}}; double x, y; @@ -217,6 +235,32 @@ static SkScalar (* const SegmentYAtT[])(const SkPoint [], double ) = { CubicYAtT }; +static SkScalar LineDXAtT(const SkPoint a[2], double ) { + return a[1].fX - a[0].fX; +} + +static SkScalar QuadDXAtT(const SkPoint a[3], double t) { + const Quadratic quad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}}; + double x; + dxdy_at_t(quad, t, x, *(double*) 0); + return SkDoubleToScalar(x); +} + +static SkScalar CubicDXAtT(const SkPoint a[4], double t) { + const Cubic cubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}, + {a[3].fX, a[3].fY}}; + double x; + dxdy_at_t(cubic, t, x, *(double*) 0); + return SkDoubleToScalar(x); +} + +static SkScalar (* const SegmentDXAtT[])(const SkPoint [], double ) = { + NULL, + LineDXAtT, + QuadDXAtT, + CubicDXAtT +}; + static void LineSubDivide(const SkPoint a[2], double startT, double endT, SkPoint sub[2]) { const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}}; @@ -416,6 +460,10 @@ public: return fDDDx * rh.fDDDy < rh.fDDDx * fDDDy; } + bool cancels(const Angle& rh) const { + return fDx * rh.fDx < 0 || fDy * rh.fDy < 0; + } + int end() const { return fEnd; } @@ -427,7 +475,7 @@ public: // since all angles share a point, this needs to know which point // is the common origin, i.e., whether the center is at pts[0] or pts[verb] // practically, this should only be called by addAngle - void set(const SkPoint* pts, SkPath::Verb verb, Segment* segment, + void set(const SkPoint* pts, SkPath::Verb verb, const Segment* segment, int start, int end) { SkASSERT(start != end); fSegment = segment; @@ -498,18 +546,13 @@ public: } Segment* segment() const { - return fSegment; + return const_cast<Segment*>(fSegment); } int sign() const { return SkSign32(fStart - fEnd); } - bool slopeEquals(const Angle& rh) const { - return fDx == rh.fDx && fDy == rh.fDy; - - } - int start() const { return fStart; } @@ -521,7 +564,7 @@ private: SkScalar fDDy; SkScalar fDDDx; SkScalar fDDDy; - Segment* fSegment; + const Segment* fSegment; int fStart; int fEnd; }; @@ -536,13 +579,32 @@ static void sortAngles(SkTDArray<Angle>& angles, SkTDArray<Angle*>& angleList) { QSort<Angle>(angleList.begin(), angleList.end() - 1); } -// Bounds, unlike Rect, does not consider a vertical line to be empty. +// Bounds, unlike Rect, does not consider a line to be empty. struct Bounds : public SkRect { static bool Intersects(const Bounds& a, const Bounds& b) { return a.fLeft <= b.fRight && b.fLeft <= a.fRight && a.fTop <= b.fBottom && b.fTop <= a.fBottom; } + void add(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom) { + if (left < fLeft) { + fLeft = left; + } + if (top < fTop) { + fTop = top; + } + if (right > fRight) { + fRight = right; + } + if (bottom > fBottom) { + fBottom = bottom; + } + } + + void add(const Bounds& toAdd) { + add(toAdd.fLeft, toAdd.fTop, toAdd.fRight, toAdd.fBottom); + } + bool isEmpty() { return fLeft > fRight || fTop > fBottom || fLeft == fRight && fTop == fBottom @@ -570,14 +632,13 @@ struct Bounds : public SkRect { }; struct Span { - double fT; Segment* fOther; + mutable SkPoint const* fPt; // lazily computed as needed + double fT; double fOtherT; // value at fOther[fOtherIndex].fT - mutable SkPoint const * fPt; // lazily computed as needed int fOtherIndex; // can't be used during intersection - int fWinding; // accumulated from contours surrounding this one - // OPTIMIZATION: coincident needs only 2 bits (values are -1, 0, 1) - int fCoincident; // -1 start of coincidence, 0 no coincidence, 1 end + int fWindSum; // accumulated from contours surrounding this one + int fWindValue; // 0 == canceled; 1 == normal; >1 == coincident bool fDone; // if set, this span to next higher T has been processed }; @@ -589,7 +650,26 @@ public: #endif } - void addAngle(SkTDArray<Angle>& angles, int start, int end) { + SkScalar activeTop() const { + SkASSERT(!done()); + int count = fTs.count(); + SkScalar result = SK_ScalarMax; + bool lastDone = true; + for (int index = 0; index < count; ++index) { + bool done = fTs[index].fDone; + if (!done || !lastDone) { + SkScalar y = yAtT(index); + if (result > y) { + result = y; + } + } + lastDone = done; + } + SkASSERT(result < SK_ScalarMax); + return result; + } + + void addAngle(SkTDArray<Angle>& angles, int start, int end) const { SkASSERT(start != end); SkPoint edge[4]; (*SegmentSubDivide[fVerb])(fPts, fTs[start].fT, fTs[end].fT, edge); @@ -603,31 +683,34 @@ public: } // FIXME: this needs to defer add for aligned consecutive line segments - SkPoint addCurveTo(int start, int end, SkPath& path) { + SkPoint addCurveTo(int start, int end, SkPath& path, bool active) { SkPoint edge[4]; + // OPTIMIZE? if not active, skip remainder and return xy_at_t(end) (*SegmentSubDivide[fVerb])(fPts, fTs[start].fT, fTs[end].fT, edge); + if (active) { #if DEBUG_PATH_CONSTRUCTION - SkDebugf("%s %s (%1.9g,%1.9g)", __FUNCTION__, - kLVerbStr[fVerb], edge[1].fX, edge[1].fY); - if (fVerb > 1) { - SkDebugf(" (%1.9g,%1.9g)", edge[2].fX, edge[2].fY); - } - if (fVerb > 2) { - SkDebugf(" (%1.9g,%1.9g)", edge[3].fX, edge[3].fY); - } - SkDebugf("\n"); + SkDebugf("%s %s (%1.9g,%1.9g)", __FUNCTION__, + kLVerbStr[fVerb], edge[1].fX, edge[1].fY); + if (fVerb > 1) { + SkDebugf(" (%1.9g,%1.9g)", edge[2].fX, edge[2].fY); + } + if (fVerb > 2) { + SkDebugf(" (%1.9g,%1.9g)", edge[3].fX, edge[3].fY); + } + SkDebugf("\n"); #endif - switch (fVerb) { - case SkPath::kLine_Verb: - path.lineTo(edge[1].fX, edge[1].fY); - break; - case SkPath::kQuad_Verb: - path.quadTo(edge[1].fX, edge[1].fY, edge[2].fX, edge[2].fY); - break; - case SkPath::kCubic_Verb: - path.cubicTo(edge[1].fX, edge[1].fY, edge[2].fX, edge[2].fY, - edge[3].fX, edge[3].fY); - break; + switch (fVerb) { + case SkPath::kLine_Verb: + path.lineTo(edge[1].fX, edge[1].fY); + break; + case SkPath::kQuad_Verb: + path.quadTo(edge[1].fX, edge[1].fY, edge[2].fX, edge[2].fY); + break; + case SkPath::kCubic_Verb: + path.cubicTo(edge[1].fX, edge[1].fY, edge[2].fX, edge[2].fY, + edge[3].fX, edge[3].fY); + break; + } } return edge[fVerb]; } @@ -637,12 +720,14 @@ public: fBounds.set(pts, 2); } - const SkPoint& addMoveTo(int tIndex, SkPath& path) { - const SkPoint& pt = xyAtT(&fTs[tIndex]); + const SkPoint& addMoveTo(int tIndex, SkPath& path, bool active) { + const SkPoint& pt = xyAtT(tIndex); + if (active) { #if DEBUG_PATH_CONSTRUCTION - SkDebugf("%s (%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY); + SkDebugf("%s (%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY); #endif - path.moveTo(pt.fX, pt.fY); + path.moveTo(pt.fX, pt.fY); + } return pt; } @@ -657,50 +742,233 @@ public: init(pts, SkPath::kQuad_Verb); fBounds.setQuadBounds(pts); } + + // Defer all coincident edge processing until + // after normal intersections have been computed + +// no need to be tricky; insert in normal T order +// resolve overlapping ts when considering coincidence later - // edges are sorted by T, then by coincidence - int addT(double newT, Segment& other, int coincident) { + // add non-coincident intersection. Resulting edges are sorted in T. + int addT(double newT, Segment* other) { // FIXME: in the pathological case where there is a ton of intercepts, // binary search? int insertedAt = -1; - Span* span; size_t tCount = fTs.count(); - for (size_t idx2 = 0; idx2 < tCount; ++idx2) { + for (size_t index = 0; index < tCount; ++index) { // OPTIMIZATION: if there are three or more identical Ts, then // the fourth and following could be further insertion-sorted so // that all the edges are clockwise or counterclockwise. // This could later limit segment tests to the two adjacent // neighbors, although it doesn't help with determining which // circular direction to go in. - if (newT < fTs[idx2].fT || (newT == fTs[idx2].fT && - coincident <= fTs[idx2].fCoincident)) { - insertedAt = idx2; - span = fTs.insert(idx2); - goto finish; + if (newT < fTs[index].fT) { + insertedAt = index; + break; } } - insertedAt = tCount; - span = fTs.append(); -finish: + Span* span; + if (insertedAt >= 0) { + span = fTs.insert(insertedAt); + } else { + insertedAt = tCount; + span = fTs.append(); + } span->fT = newT; - span->fOther = &other; + span->fOther = other; span->fPt = NULL; - span->fWinding = 0; - if (span->fDone = newT == 1) { + span->fWindSum = SK_MinS32; + span->fWindValue = 1; + if ((span->fDone = newT == 1)) { ++fDoneSpans; } - span->fCoincident = coincident; - fCoincident |= coincident; // OPTIMIZATION: ever used? return insertedAt; } - void addTwoAngles(int start, int end, SkTDArray<Angle>& angles) { + // set spans from start to end to decrement by one + // note this walks other backwards + // FIMXE: there's probably an edge case that can be constructed where + // two span in one segment are separated by float epsilon on one span but + // not the other, if one segment is very small. For this + // case the counts asserted below may or may not be enough to separate the + // spans. Even if the counts work out, what if the spanw aren't correctly + // sorted? It feels better in such a case to match the span's other span + // pointer since both coincident segments must contain the same spans. + void addTCancel(double startT, double endT, Segment& other, + double oStartT, double oEndT) { + SkASSERT(endT - startT >= FLT_EPSILON); + SkASSERT(oEndT - oStartT >= FLT_EPSILON); + int index = 0; + while (startT - fTs[index].fT >= FLT_EPSILON) { + ++index; + } + int oCount = other.fTs.count(); + while (other.fTs[--oCount].fT - oEndT >= FLT_EPSILON) + ; + int oIndex = oCount; + while (other.fTs[--oIndex].fT - oEndT > -FLT_EPSILON) + ; + Span* test = &fTs[index]; + Span* oTest = &other.fTs[oIndex]; + SkDEBUGCODE(int testWindValue = test->fWindValue); + SkDEBUGCODE(int oTestWindValue = oTest->fWindValue); + SkDEBUGCODE(int startIndex = index); + SkTDArray<double> outsideTs; + SkTDArray<double> oOutsideTs; + do { + bool decrement = test->fWindValue && oTest->fWindValue; + Span* end = test; + double startT = end->fT; + double oStartT = oTest->fT; + do { + SkASSERT(testWindValue == end->fWindValue); + if (decrement) { + if (--(end->fWindValue) == 0) { + end->fDone = true; + ++fDoneSpans; + *outsideTs.append() = end->fT; + *outsideTs.append() = oStartT; + } + } + end = &fTs[++index]; + } while (end->fT - test->fT < FLT_EPSILON); + SkASSERT(oCount - oIndex == index - startIndex); + Span* oTestStart = oTest; + SkDEBUGCODE(oCount = oIndex); + do { + SkASSERT(oTestWindValue == oTestStart->fWindValue); + if (decrement) { + if (--(oTestStart->fWindValue) == 0) { + oTestStart->fDone = true; + ++other.fDoneSpans; + *oOutsideTs.append() = oTestStart->fT; + *oOutsideTs.append() = startT; + } + } + if (!oIndex) { + break; + } + oTestStart = &other.fTs[--oIndex]; + } while (oTest->fT - oTestStart->fT < FLT_EPSILON); + test = end; + oTest = oTestStart; + } while (test->fT < endT - FLT_EPSILON); + SkASSERT(!oIndex || oTest->fT <= oStartT - FLT_EPSILON); +#if 0 + if (!done() && outsideTs.count()) { + addTOutsides(outsideTs, &other, oStartT); + } + if (!other.done() && oOutsideTs.count()) { + other.addTOutsides(oOutsideTs, this, startT); + } +#endif + } + + // set spans from start to end to increment the greater by one and decrement + // the lesser + void addTCoincident(double startT, double endT, Segment& other, + double oStartT, double oEndT) { + SkASSERT(endT - startT >= FLT_EPSILON); + SkASSERT(oEndT - oStartT >= FLT_EPSILON); + int index = 0; + while (startT - fTs[index].fT >= FLT_EPSILON) { + ++index; + } + int oIndex = 0; + while (oStartT - other.fTs[oIndex].fT >= FLT_EPSILON) { + ++oIndex; + } + Span* test = &fTs[index]; + Span* oTest = &other.fTs[oIndex]; + SkDEBUGCODE(int testWindValue = test->fWindValue); + SkDEBUGCODE(int oTestWindValue = oTest->fWindValue); + SkTDArray<double> outsideTs; + SkTDArray<double> oOutsideTs; + do { + bool decrementOther = test->fWindValue >= oTest->fWindValue; + Span* end = test; + double startT = end->fT; + double oStartT = oTest->fT; + do { + SkASSERT(testWindValue == end->fWindValue); + if (decrementOther) { + ++(end->fWindValue); + } else { + if (--(end->fWindValue) == 0) { + end->fDone = true; + ++fDoneSpans; + *outsideTs.append() = end->fT; + *outsideTs.append() = oStartT; + } + } + end = &fTs[++index]; + } while (end->fT - test->fT < FLT_EPSILON); + Span* oEnd = oTest; + do { + SkASSERT(oTestWindValue == oEnd->fWindValue); + if (decrementOther) { + if (--(oEnd->fWindValue) == 0) { + oEnd->fDone = true; + ++other.fDoneSpans; + *oOutsideTs.append() = oEnd->fT; + *oOutsideTs.append() = startT; + } + } else { + ++(oEnd->fWindValue); + } + oEnd = &other.fTs[++oIndex]; + } while (oEnd->fT - oTest->fT < FLT_EPSILON); + test = end; + oTest = oEnd; + } while (test->fT < endT - FLT_EPSILON); + SkASSERT(oTest->fT < oEndT + FLT_EPSILON); + SkASSERT(oTest->fT > oEndT - FLT_EPSILON); + if (!done() && outsideTs.count()) { + addTOutsides(outsideTs, &other, oEndT); + } + if (!other.done() && oOutsideTs.count()) { + other.addTOutsides(oOutsideTs, this, endT); + } + } + + void addTOutsides(const SkTDArray<double>& outsideTs, Segment* other, + double otherEnd) { + int count = outsideTs.count(); + double endT = 0; + int endSpan = 0; + for (int index = 0; index < count; index += 2) { + double t = outsideTs[index]; + double otherT = outsideTs[index + 1]; + if (t > 1 - FLT_EPSILON) { + return; + } + if (t - endT > FLT_EPSILON) { + endSpan = addTPair(t, other, otherT); + } + do { + endT = fTs[++endSpan].fT; + } while (endT - t < FLT_EPSILON); + } + addTPair(endT, other, otherEnd); + } + + int addTPair(double t, Segment* other, double otherT) { + int insertedAt = addT(t, other); + int otherInsertedAt = other->addT(otherT, this); + addOtherT(insertedAt, otherT, otherInsertedAt); + other->addOtherT(otherInsertedAt, t, insertedAt); + return insertedAt; + } + + void addTwoAngles(int start, int end, SkTDArray<Angle>& angles) const { // add edge leading into junction - addAngle(angles, end, start); + if (fTs[SkMin32(end, start)].fWindValue > 0) { + addAngle(angles, end, start); + } // add edge leading away from junction int step = SkSign32(end - start); int tIndex = nextSpan(end, step); - if (tIndex >= 0) { + if (tIndex >= 0 && fTs[SkMin32(end, tIndex)].fWindValue > 0) { addAngle(angles, end, tIndex); } } @@ -710,15 +978,14 @@ finish: } void buildAngles(int index, SkTDArray<Angle>& angles) const { - SkASSERT(!done()); double referenceT = fTs[index].fT; int lesser = index; - while (--lesser >= 0 && referenceT == fTs[lesser].fT) { + while (--lesser >= 0 && referenceT - fTs[lesser].fT < FLT_EPSILON) { buildAnglesInner(lesser, angles); } do { buildAnglesInner(index, angles); - } while (++index < fTs.count() && referenceT == fTs[index].fT); + } while (++index < fTs.count() && fTs[index].fT - referenceT < FLT_EPSILON); } void buildAnglesInner(int index, SkTDArray<Angle>& angles) const { @@ -731,15 +998,22 @@ finish: int oIndex = span->fOtherIndex; // if done == -1, prior span has already been processed int step = 1; - int next = other->nextSpanEnd(oIndex, step); + int next = other->nextSpan(oIndex, step); if (next < 0) { step = -step; - next = other->nextSpanEnd(oIndex, step); + next = other->nextSpan(oIndex, step); } - oIndex = other->coincidentEnd(oIndex, -step); // add candidate into and away from junction other->addTwoAngles(next, oIndex, angles); - } + } + + // OPTIMIZATION: inefficient, refactor + bool cancels(const Segment& other) const { + SkTDArray<Angle> angles; + addAngle(angles, 0, fTs.count() - 1); + other.addAngle(angles, 0, other.fTs.count() - 1); + return angles[0].cancels(angles[1]); + } // figure out if the segment's ascending T goes clockwise or not // not enough context to write this as shown @@ -752,45 +1026,41 @@ finish: return false; } - static bool Coincident(const Angle* current, const Angle* next) { - const Segment* segment = current->segment(); - const Span& span = segment->fTs[current->start()]; - if (!span.fCoincident) { - return false; - } - const Segment* nextSegment = next->segment(); - const Span& nextSpan = nextSegment->fTs[next->start()]; - if (!nextSpan.fCoincident) { - return false; - } - // use angle dx/dy instead of other since 3 or more may be coincident - return current->slopeEquals(*next); - } - - static bool CoincidentCancels(const Angle* current, const Angle* next) { - return SkSign32(current->start() - current->end()) - != SkSign32(next->start() - next->end()); - } - - int coincidentEnd(int from, int step) const { - double fromT = fTs[from].fT; - int count = fTs.count(); - int to = from; - while (step > 0 ? ++to < count : --to >= 0) { - const Span& span = fTs[to]; - if (fromT != span.fT) { - // FIXME: we assume that if the T changes, we don't care about - // coincident -- but in nextSpan, we require that both the T - // and actual loc change to represent a span. This asymettry may - // be OK or may be trouble -- if trouble, probably will need to - // detect coincidence earlier or sort differently - break; + int crossedSpan(const SkPoint& basePt, SkScalar& bestY, double& hitT) const { + int start = 0; + int bestT = -1; + SkScalar top = bounds().fTop; + SkScalar bottom = bounds().fBottom; + int end; + do { + end = nextSpan(start, 1); + SkPoint edge[4]; + // OPTIMIZE: wrap this so that if start==0 end==fTCount-1 we can + // work with the original data directly + (*SegmentSubDivide[fVerb])(fPts, fTs[start].fT, fTs[end].fT, edge); + // start here; intersect ray starting at basePt with edge + Intersections intersections; + int pts = (*VSegmentIntersect[fVerb])(edge, top, bottom, basePt.fX, + false, intersections); + if (pts == 0) { + continue; } - if (span.fCoincident == step) { - return to; + if (pts > 1 && fVerb == SkPath::kLine_Verb) { + // if the intersection is edge on, wait for another one + continue; } - } - return from; + SkASSERT(pts == 1); // FIXME: more code required to disambiguate + SkPoint pt; + double foundT = intersections.fT[0][0]; + (*SegmentXYAtT[fVerb])(fPts, foundT, &pt); + if (bestY < pt.fY) { + bestY = pt.fY; + bestT = foundT < 1 ? start : end; + hitT = foundT; + } + start = end; + } while (fTs[end].fT != 1); + return bestT; } bool done() const { @@ -798,37 +1068,46 @@ finish: return fDoneSpans == fTs.count(); } + // so the span needs to contain the pairing info found here + // this should include the winding computed for the edge, and + // what edge it connects to, and whether it is discarded + // (maybe discarded == abs(winding) > 1) ? + // only need derivatives for duration of sorting, add a new struct + // for pairings, remove extra spans that have zero length and + // reference an unused other + // for coincident, the last span on the other may be marked done + // (always?) + + // if loop is exhausted, contour may be closed. + // FIXME: pass in close point so we can check for closure + + // given a segment, and a sense of where 'inside' is, return the next + // segment. If this segment has an intersection, or ends in multiple + // segments, find the mate that continues the outside. + // note that if there are multiples, but no coincidence, we can limit + // choices to connections in the correct direction + + // mark found segments as done + // start is the index of the beginning T of this edge // it is guaranteed to have an end which describes a non-zero length (?) // winding -1 means ccw, 1 means cw - // step is in/out -1 or 1 - // spanIndex is returned + // firstFind allows coincident edges to be treated differently Segment* findNext(int winding, const int startIndex, const int endIndex, - int& nextStart, int& nextEnd) { + int& nextStart, int& nextEnd, bool firstFind) { SkASSERT(startIndex != endIndex); int count = fTs.count(); SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0); - int step = SkSign32(endIndex - startIndex); - int end = nextSpanEnd(startIndex, step); + int end = nextSpan(startIndex, step); SkASSERT(end >= 0); - - // preflight for coincidence -- if present, it may change winding - // considerations and whether reversed edges can be followed - - // Discard opposing direction candidates if no coincidence was found. Span* endSpan = &fTs[end]; Segment* other; - if (isSimple(end, step)) { + if (isSimple(end)) { // mark the smaller of startIndex, endIndex done, and all adjacent // spans with the same T value (but not 'other' spans) markDone(SkMin32(startIndex, endIndex), winding); - // move in winding direction until edge in correct direction - // balance wrong direction edges before finding correct one - // this requres that the intersection is angularly sorted - // for a single intersection, special case -- choose the opposite - // edge that steps the same other = endSpan->fOther; nextStart = endSpan->fOtherIndex; nextEnd = nextStart + step; @@ -857,106 +1136,65 @@ finish: } } // back up if prior edge is coincident with firstIndex - int prior = firstIndex; - do { - if (--prior < 0) { - prior = angleCount - 1; - } - SkASSERT(prior != angleIndex); - if (!Coincident(sorted[prior], sorted[firstIndex])) { - break; - } - firstIndex = prior; - angle = sorted[prior]; - } while (true); - - // put some thought into handling coincident edges - // 1) defer the initial moveTo/curveTo until we know that firstIndex - // isn't coincident (although maybe findTop could tell us that) - // 2) allow the below to mark and skip coincident pairs - // 3) return something (null?) if all segments cancel each other out - // 4) if coincident edges don't cancel, figure out which to return (follow) - + // adjustFirst(sorted, firstIndex, winding, firstFind); SkASSERT(firstIndex >= 0); int startWinding = winding; - int nextIndex = firstIndex; - const Angle* nextAngle; - Segment* nextSegment; + int nextIndex = firstIndex + 1; + int lastIndex = firstIndex != 0 ? firstIndex : angleCount; + const Angle* foundAngle = NULL; + // bool alreadyMarked = angle->segment()->fTs[SkMin32(angle->start(), + // angle->end())].fDone; + // iterate through the angle, and compute everyone's winding + bool firstEdge = true; do { - if (++nextIndex == angleCount) { + if (nextIndex == angleCount) { nextIndex = 0; } - SkASSERT(nextIndex != firstIndex); // should never wrap around - nextAngle = sorted[nextIndex]; + const Angle* nextAngle = sorted[nextIndex]; int maxWinding = winding; - winding -= nextAngle->sign(); - nextSegment = nextAngle->segment(); - if (nextSegment->done()) { - if (!winding) { - break; - } - continue; - } - bool pairCoincides = Coincident(angle, nextAngle); - bool pairCancels = pairCoincides - && CoincidentCancels(angle, nextAngle); - if (pairCancels) { - angle->segment()->markAndChaseCoincident(angle->start(), - angle->end(), nextSegment); - return NULL; - } + Segment* nextSegment = nextAngle->segment(); + int windValue = nextSegment->windValue(nextAngle); + SkASSERT(windValue > 0); + winding -= nextAngle->sign() * windValue; + firstEdge = false; if (!winding) { - break; - } - if (pairCoincides) { - bool markNext = abs(maxWinding) < abs(winding); - if (markNext) { - nextSegment->markDone(SkMin32(nextAngle->start(), - nextAngle->end()), winding); - } else { - angle->segment()->markDone(SkMin32(angle->start(), - angle->end()), maxWinding); + if (!foundAngle) { + foundAngle = nextAngle; } + goto doNext; + } + if (nextSegment->done()) { + goto doNext; } // if the winding is non-zero, nextAngle does not connect to // current chain. If we haven't done so already, mark the angle // as done, record the winding value, and mark connected unambiguous // segments as well. - else if (nextSegment->winding(nextAngle) == 0) { + if (nextSegment->winding(nextAngle) == SK_MinS32) { if (abs(maxWinding) < abs(winding)) { maxWinding = winding; } - nextSegment->markAndChaseWinding(nextAngle, maxWinding); + if (foundAngle) { + nextSegment->markAndChaseWinding(nextAngle, maxWinding); + } else { + nextSegment->markAndChaseDone(nextAngle, maxWinding); + } } - } while ((angle = nextAngle)); // always true - markDone(SkMin32(startIndex, endIndex), startWinding); - nextStart = nextAngle->start(); - nextEnd = nextAngle->end(); - return nextSegment; + doNext: + angle = nextAngle; + } while (++nextIndex != lastIndex); + // if (!alreadyMarked) { + sorted[firstIndex]->segment()-> + markDone(SkMin32(startIndex, endIndex), startWinding); + // } + if (!foundAngle) { + return NULL; + } + nextStart = foundAngle->start(); + nextEnd = foundAngle->end(); + return foundAngle->segment(); } - - // so the span needs to contain the pairing info found here - // this should include the winding computed for the edge, and - // what edge it connects to, and whether it is discarded - // (maybe discarded == abs(winding) > 1) ? - // only need derivatives for duration of sorting, add a new struct - // for pairings, remove extra spans that have zero length and - // reference an unused other - // for coincident, the last span on the other may be marked done - // (always?) - - // if loop is exhausted, contour may be closed. - // FIXME: pass in close point so we can check for closure - - // given a segment, and a sense of where 'inside' is, return the next - // segment. If this segment has an intersection, or ends in multiple - // segments, find the mate that continues the outside. - // note that if there are multiples, but no coincidence, we can limit - // choices to connections in the correct direction - - // mark found segments as done - // FIXME: this is tricky code; needs its own unit test void findTooCloseToCall(int /* winding */ ) { // FIXME: winding should be considered int count = fTs.count(); @@ -985,7 +1223,7 @@ finish: // so, the span from here to there is coincident. for (int index = matchIndex + 1; index < count; ++index) { Span* test = &fTs[index]; - if (test->fCoincident) { + if (test->fDone) { continue; } Segment* tOther = test->fOther; @@ -1007,7 +1245,7 @@ finish: double moStartT, moEndT; for (int moIndex = 0; moIndex < moCount; ++moIndex) { Span& moSpan = mOther->fTs[moIndex]; - if (moSpan.fCoincident) { + if (moSpan.fDone) { continue; } if (moSpan.fOther == this) { @@ -1060,11 +1298,16 @@ finish: || !tOther->isLinear(toStart, toEnd)) { continue; } - // FIXME: may need to resort if we depend on coincidence first, last - mOther->fTs[moStart].fCoincident = -1; - tOther->fTs[toStart].fCoincident = -1; - mOther->fTs[moEnd].fCoincident = 1; - tOther->fTs[toEnd].fCoincident = 1; + // FIXME: defer implementation until the rest works + // this may share code with regular coincident detection + SkASSERT(0); + #if 0 + if (flipped) { + mOther->addTCancel(moStart, moEnd, tOther, tStart, tEnd); + } else { + mOther->addTCoincident(moStart, moEnd, tOther, tStart, tEnd); + } + #endif } } @@ -1077,10 +1320,10 @@ finish: SkASSERT(!done()); int firstT; int lastT; - int firstCoinT; SkPoint topPt; topPt.fY = SK_ScalarMax; int count = fTs.count(); + // see if either end is not done since we want smaller Y of the pair bool lastDone = true; for (int index = 0; index < count; ++index) { const Span& span = fTs[index]; @@ -1089,22 +1332,13 @@ finish: if (topPt.fY > intercept.fY || (topPt.fY == intercept.fY && topPt.fX > intercept.fX)) { topPt = intercept; - firstT = lastT = firstCoinT = index; + firstT = lastT = index; } else if (topPt == intercept) { lastT = index; - if (span.fCoincident) { - firstCoinT = index; - } } } lastDone = span.fDone; } - // if there's only a pair of segments, go with the endpoint chosen above - if (firstT == lastT) { - tIndex = firstT; - endIndex = firstT > 0 ? tIndex - 1 : tIndex + 1; - return this; - } // sort the edges to find the leftmost int step = 1; int end = nextSpan(firstT, step); @@ -1117,7 +1351,7 @@ finish: // look for left-ness from tLeft to firstT (matching y of other) SkTDArray<Angle> angles; SkASSERT(firstT - end != 0); - addTwoAngles(end, firstCoinT, angles); + addTwoAngles(end, firstT, angles); buildAngles(firstT, angles); SkTDArray<Angle*> sorted; sortAngles(angles, sorted); @@ -1150,93 +1384,27 @@ finish: Span& oSpan = other->fTs[o]; if (oT == oSpan.fT && this == oSpan.fOther) { iSpan.fOtherIndex = o; + break; } } } } // OPTIMIZATION: uses tail recursion. Unwise? - void innerCoincidentChase(int step, Segment* other) { - // find other at index - SkASSERT(!done()); - const Span* start = NULL; - const Span* end = NULL; - int index, startIndex, endIndex; - int count = fTs.count(); - for (index = 0; index < count; ++index) { - const Span& span = fTs[index]; - if (!span.fCoincident || span.fOther != other) { - continue; - } - if (!start) { - if (span.fDone) { - continue; - } - startIndex = index; - start = &span; - } else { - SkASSERT(!end); - endIndex = index; - end = &span; - } - } - if (!end) { + void innerChaseDone(int index, int step, int winding) { + int end = nextSpan(index, step); + if (multipleSpans(end, step)) { return; } - Segment* next; - Segment* nextOther; - if (step < 0) { - next = start->fT == 0 ? NULL : this; - nextOther = other->fTs[start->fOtherIndex].fT == 1 ? NULL : other; - } else { - next = end->fT == 1 ? NULL : this; - nextOther = other->fTs[end->fOtherIndex].fT == 0 ? NULL : other; - } - SkASSERT(!next || !nextOther); - for (index = 0; index < count; ++index) { - const Span& span = fTs[index]; - if (span.fCoincident || span.fOther == other) { - continue; - } - bool checkNext = !next && (step < 0 ? span.fT == 0 - && span.fOtherT == 1 : span.fT == 1 && span.fOtherT == 0); - bool checkOther = !nextOther && (step < 0 ? span.fT == start->fT - && span.fOtherT == 0 : span.fT == end->fT && span.fOtherT == 1); - if (!checkNext && !checkOther) { - continue; - } - Segment* oSegment = span.fOther; - if (oSegment->done()) { - continue; - } - int oCount = oSegment->fTs.count(); - for (int oIndex = 0; oIndex < oCount; ++oIndex) { - const Span& oSpan = oSegment->fTs[oIndex]; - if (oSpan.fT > 0 && oSpan.fT < 1) { - continue; - } - if (!oSpan.fCoincident) { - continue; - } - if (checkNext && (oSpan.fT == 0 ^ step < 0)) { - next = oSegment; - checkNext = false; - } - if (checkOther && (oSpan.fT == 1 ^ step < 0)) { - nextOther = oSegment; - checkOther = false; - } - } - } - markDone(SkMin32(startIndex, endIndex), 0); - other->markDone(SkMin32(start->fOtherIndex, end->fOtherIndex), 0); - if (next && nextOther) { - next->innerCoincidentChase(step, nextOther); - } + const Span& endSpan = fTs[end]; + Segment* other = endSpan.fOther; + index = endSpan.fOtherIndex; + int otherEnd = other->nextSpan(index, step); + other->innerChaseDone(index, step, winding); + other->markDone(SkMin32(index, otherEnd), winding); } - // OPTIMIZATION: uses tail recursion. Unwise? - void innerChase(int index, int step, int winding) { + void innerChaseWinding(int index, int step, int winding) { int end = nextSpan(index, step); if (multipleSpans(end, step)) { return; @@ -1245,15 +1413,19 @@ finish: Segment* other = endSpan.fOther; index = endSpan.fOtherIndex; int otherEnd = other->nextSpan(index, step); - other->innerChase(index, step, winding); - other->markDone(SkMin32(index, otherEnd), winding); + int min = SkMin32(index, otherEnd); + if (other->fTs[min].fWindSum != SK_MinS32) { + SkASSERT(other->fTs[index].fWindSum == winding); + return; + } + other->innerChaseWinding(index, step, winding); + other->markWinding(min, winding); } void init(const SkPoint pts[], SkPath::Verb verb) { fPts = pts; fVerb = verb; fDoneSpans = 0; - fCoincident = 0; } bool intersected() const { @@ -1276,27 +1448,19 @@ finish: } } - bool isSimple(int index, int step) const { + bool isSimple(int end) const { int count = fTs.count(); if (count == 2) { return true; } - double spanT = fTs[index].fT; - if (spanT > 0 && spanT < 1) { - return false; - } - if (step < 0) { - const Span& secondSpan = fTs[1]; - if (secondSpan.fT == 0) { - return false; - } - return xyAtT(&fTs[0]) != xyAtT(&secondSpan); + double t = fTs[end].fT; + if (t < FLT_EPSILON) { + return fTs[1].fT >= FLT_EPSILON; } - const Span& penultimateSpan = fTs[count - 2]; - if (penultimateSpan.fT == 1) { - return false; + if (t > 1 - FLT_EPSILON) { + return fTs[count - 2].fT <= 1 - FLT_EPSILON; } - return xyAtT(&fTs[count - 1]) != xyAtT(&penultimateSpan); + return false; } bool isHorizontal() const { @@ -1311,51 +1475,101 @@ finish: return (*SegmentLeftMost[fVerb])(fPts, fTs[start].fT, fTs[end].fT); } - void markAndChaseCoincident(int index, int endIndex, Segment* other) { - int step = SkSign32(endIndex - index); - innerCoincidentChase(step, other); - } - // this span is excluded by the winding rule -- chase the ends // as long as they are unambiguous to mark connections as done // and give them the same winding value - void markAndChaseWinding(const Angle* angle, int winding) { + void markAndChaseDone(const Angle* angle, int winding) { int index = angle->start(); int endIndex = angle->end(); int step = SkSign32(endIndex - index); - innerChase(index, step, winding); + innerChaseDone(index, step, winding); markDone(SkMin32(index, endIndex), winding); } + void markAndChaseWinding(const Angle* angle, int winding) { + int index = angle->start(); + int endIndex = angle->end(); + int min = SkMin32(index, endIndex); + int step = SkSign32(endIndex - index); + innerChaseWinding(index, step, winding); + markWinding(min, winding); + } + // FIXME: this should also mark spans with equal (x,y) + // This may be called when the segment is already marked done. While this + // wastes time, it shouldn't do any more than spin through the T spans. + // OPTIMIZATION: abort on first done found (assuming that this code is + // always called to mark segments done). void markDone(int index, int winding) { - SkASSERT(!done()); + // SkASSERT(!done()); double referenceT = fTs[index].fT; int lesser = index; - while (--lesser >= 0 && referenceT == fTs[lesser].fT) { + while (--lesser >= 0 && referenceT - fTs[lesser].fT < FLT_EPSILON) { Span& span = fTs[lesser]; - // FIXME: this needs to assert that all are not done or one or more are - // coincident - // SkASSERT(!span.fDone || span.fCoincident); if (span.fDone) { continue; } + #if DEBUG_MARK_DONE + const SkPoint& pt = xyAtT(&span); + SkDebugf("%s segment=%d index=%d t=%1.9g pt=(%1.9g,%1.9g) wind=%d\n", + __FUNCTION__, fID, lesser, span.fT, pt.fX, pt.fY, winding); + #endif span.fDone = true; - SkASSERT(!span.fWinding || span.fWinding == winding); - span.fWinding = winding; + SkASSERT(span.fWindSum == SK_MinS32 || span.fWindSum == winding); + span.fWindSum = winding; fDoneSpans++; } do { Span& span = fTs[index]; - // SkASSERT(!span.fDone || span.fCoincident); + // SkASSERT(!span.fDone); if (span.fDone) { continue; } + #if DEBUG_MARK_DONE + const SkPoint& pt = xyAtT(&span); + SkDebugf("%s segment=%d index=%d t=%1.9g pt=(%1.9g,%1.9g) wind=%d\n", + __FUNCTION__, fID, index, span.fT, pt.fX, pt.fY, winding); + #endif span.fDone = true; - SkASSERT(!span.fWinding || span.fWinding == winding); - span.fWinding = winding; + SkASSERT(span.fWindSum == SK_MinS32 || span.fWindSum == winding); + span.fWindSum = winding; fDoneSpans++; - } while (++index < fTs.count() && referenceT == fTs[index].fT); + } while (++index < fTs.count() && fTs[index].fT - referenceT < FLT_EPSILON); + } + + void markWinding(int index, int winding) { + SkASSERT(!done()); + double referenceT = fTs[index].fT; + int lesser = index; + while (--lesser >= 0 && referenceT - fTs[lesser].fT < FLT_EPSILON) { + Span& span = fTs[lesser]; + if (span.fDone) { + continue; + } + SkASSERT(span.fWindValue == 1 || winding == 0); + SkASSERT(span.fWindSum == SK_MinS32 || span.fWindSum == winding); + #if DEBUG_MARK_DONE + const SkPoint& pt = xyAtT(&span); + SkDebugf("%s segment=%d index=%d t=%1.9g pt=(%1.9g,%1.9g) wind=%d\n", + __FUNCTION__, fID, lesser, span.fT, pt.fX, pt.fY, winding); + #endif + span.fWindSum = winding; + } + do { + Span& span = fTs[index]; + // SkASSERT(!span.fDone || span.fCoincident); + if (span.fDone) { + continue; + } + SkASSERT(span.fWindValue == 1 || winding == 0); + SkASSERT(span.fWindSum == SK_MinS32 || span.fWindSum == winding); + #if DEBUG_MARK_DONE + const SkPoint& pt = xyAtT(&span); + SkDebugf("%s segment=%d index=%d t=%1.9g pt=(%1.9g,%1.9g) wind=%d\n", + __FUNCTION__, fID, index, span.fT, pt.fX, pt.fY, winding); + #endif + span.fWindSum = winding; + } while (++index < fTs.count() && fTs[index].fT - referenceT < FLT_EPSILON); } bool multipleSpans(int end, int step) const { @@ -1368,18 +1582,14 @@ finish: // coincidence is found when the beginning Ts contain -step and the end // contains step. When it is looking for the beginning of the next, the // first Ts found can be ignored and the last Ts should contain -step. + // OPTIMIZATION: probably should split into two functions int nextSpan(int from, int step) const { const Span& fromSpan = fTs[from]; int count = fTs.count(); int to = from; while (step > 0 ? ++to < count : --to >= 0) { const Span& span = fTs[to]; - if (fromSpan.fT == span.fT) { - continue; - } - const SkPoint& loc = xyAtT(&span); - const SkPoint& fromLoc = xyAtT(&fromSpan); - if (fromLoc == loc) { + if ((step > 0 ? span.fT - fromSpan.fT : fromSpan.fT - span.fT) < FLT_EPSILON) { continue; } return to; @@ -1387,16 +1597,6 @@ finish: return -1; } - // once past current span, if step>0, look for coicident==1 - // if step<0, look for coincident==-1 - int nextSpanEnd(int from, int step) const { - int result = nextSpan(from, step); - if (result < 0) { - return result; - } - return coincidentEnd(result, step); - } - const SkPoint* pts() const { return fPts; } @@ -1411,6 +1611,11 @@ finish: const Span& span(int tIndex) const { return fTs[tIndex]; } + + int spanSign(int startIndex, int endIndex) const { + return startIndex < endIndex ? -fTs[startIndex].fWindValue : + fTs[endIndex].fWindValue; + } // OPTIMIZATION: mark as debugging only if used solely by tests double t(int tIndex) const { @@ -1424,18 +1629,64 @@ finish: SkPath::Verb verb() const { return fVerb; } + + // if the only remaining spans are small, ignore them, and mark done + bool virtuallyDone() { + int count = fTs.count(); + double previous = 0; + bool previousDone = fTs[0].fDone; + for (int index = 1; index < count; ++index) { + Span& span = fTs[index]; + double t = span.fT; + if (t - previous < FLT_EPSILON) { + if (span.fDone && !previousDone) { + int prior = --index; + int winding = span.fWindSum; + do { + Span& priorSpan = fTs[prior]; + priorSpan.fDone = true; + priorSpan.fWindSum = winding; + fDoneSpans++; + } while (--prior >= 0 && t - fTs[prior].fT < FLT_EPSILON); + } + } else if (!previousDone) { + return false; + } + previous = t; + previousDone = span.fDone; + } + SkASSERT(done()); + return true; + } + + int winding(int tIndex) const { + return fTs[tIndex].fWindSum; + } - bool winding(const Angle* angle) { + int winding(const Angle* angle) const { int start = angle->start(); int end = angle->end(); int index = SkMin32(start, end); - Span& span = fTs[index]; - return span.fWinding; + return winding(index); + } + + int windValue(int tIndex) const { + return fTs[tIndex].fWindValue; + } + + int windValue(const Angle* angle) const { + int start = angle->start(); + int end = angle->end(); + int index = SkMin32(start, end); + return windValue(index); + } + + SkScalar xAtT(const Span* span) const { + return xyAtT(span).fX; } - SkScalar xAtT(double t) const { - SkASSERT(t >= 0 && t <= 1); - return (*SegmentXAtT[fVerb])(fPts, t); + const SkPoint& xyAtT(int index) const { + return xyAtT(&fTs[index]); } const SkPoint& xyAtT(const Span* span) const { @@ -1452,10 +1703,13 @@ finish: } return *span->fPt; } + + SkScalar yAtT(int index) const { + return yAtT(&fTs[index]); + } - SkScalar yAtT(double t) const { - SkASSERT(t >= 0 && t <= 1); - return (*SegmentYAtT[fVerb])(fPts, t); + SkScalar yAtT(const Span* span) const { + return xyAtT(span).fY; } #if DEBUG_DUMP @@ -1466,10 +1720,10 @@ finish: SkPoint out; (*SegmentXYAtT[fVerb])(fPts, t(i), &out); SkDebugf("%*s [%d] %s.fTs[%d]=%1.9g (%1.9g,%1.9g) other=%d" - " otherT=%1.9g winding=%d\n", + " otherT=%1.9g windSum=%d\n", tab + sizeof(className), className, fID, kLVerbStr[fVerb], i, fTs[i].fT, out.fX, out.fY, - fTs[i].fOther->fID, fTs[i].fOtherT, fTs[i].fWinding); + fTs[i].fOther->fID, fTs[i].fOtherT, fTs[i].fWindSum); } SkDebugf("%*s [%d] fBounds=(l:%1.9g, t:%1.9g r:%1.9g, b:%1.9g)", tab + sizeof(className), className, fID, @@ -1486,14 +1740,17 @@ private: // be allocated as needed instead of always initialized -- though maybe // the initialization is lightweight enough that it hardly matters mutable SkTDArray<SkPoint> fIntersections; - // FIXME: coincident only needs two bits (-1, 0, 1) - int fCoincident; // non-zero if some coincident span inside int fDoneSpans; // used for quick check that segment is finished #if DEBUG_DUMP int fID; #endif }; +struct Coincidence { + Segment* fSegments[2]; + double fTs[2][2]; +}; + class Contour { public: Contour() { @@ -1509,6 +1766,26 @@ public: : fBounds.fTop < rh.fBounds.fTop; } + void addCoincident(int index, Contour* other, int otherIndex, + const Intersections& ts, bool swap) { + Coincidence& coincidence = *fCoincidences.append(); + coincidence.fSegments[0] = &fSegments[index]; + coincidence.fSegments[1] = &other->fSegments[otherIndex]; + coincidence.fTs[swap][0] = ts.fT[0][0]; + coincidence.fTs[swap][1] = ts.fT[0][1]; + coincidence.fTs[!swap][0] = ts.fT[1][0]; + coincidence.fTs[!swap][1] = ts.fT[1][1]; + } + + void addCross(const Contour* crosser) { +#ifdef DEBUG_CROSS + for (int index = 0; index < fCrosses.count(); ++index) { + SkASSERT(fCrosses[index] != crosser); + } +#endif + *fCrosses.append() = crosser; + } + void addCubic(const SkPoint pts[4]) { fSegments.push_back().addCubic(pts); fContainsCurves = true; @@ -1518,6 +1795,10 @@ public: fSegments.push_back().addLine(pts); return fSegments.count(); } + + void addOtherT(int segIndex, int tIndex, double otherT, int otherIndex) { + fSegments[segIndex].addOtherT(tIndex, otherT, otherIndex); + } int addQuad(const SkPoint pts[3]) { fSegments.push_back().addQuad(pts); @@ -1525,6 +1806,11 @@ public: return fSegments.count(); } + int addT(int segIndex, double newT, Contour* other, int otherIndex) { + containsIntercepts(); + return fSegments[segIndex].addT(newT, &other->fSegments[otherIndex]); + } + const Bounds& bounds() const { return fBounds; } @@ -1538,6 +1824,48 @@ public: fContainsIntercepts = true; } + const Segment* crossedSegment(const SkPoint& basePt, SkScalar& bestY, + int &tIndex, double& hitT) { + int segmentCount = fSegments.count(); + const Segment* bestSegment = NULL; + for (int test = 0; test < segmentCount; ++test) { + Segment* testSegment = &fSegments[test]; + const SkRect& bounds = testSegment->bounds(); + if (bounds.fTop < bestY) { + continue; + } + if (bounds.fTop > basePt.fY) { + continue; + } + if (bounds.fLeft > basePt.fX) { + continue; + } + if (bounds.fRight < basePt.fX) { + continue; + } + double testHitT; + int testT = testSegment->crossedSpan(basePt, bestY, testHitT); + if (testT >= 0) { + bestSegment = testSegment; + tIndex = testT; + hitT = testHitT; + } + } + return bestSegment; + } + + bool crosses(const Contour* crosser) const { + if (this == crosser) { + return true; + } + for (int index = 0; index < fCrosses.count(); ++index) { + if (fCrosses[index] == crosser) { + return true; + } + } + return false; + } + void findTooCloseToCall(int winding) { int segmentCount = fSegments.count(); for (int sIndex = 0; sIndex < segmentCount; ++sIndex) { @@ -1556,44 +1884,118 @@ public: fSegments.reset(); fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax); fContainsCurves = fContainsIntercepts = false; + fWindingSum = -1; } + void resolveCoincidence(int winding) { + int count = fCoincidences.count(); + for (int index = 0; index < count; ++index) { + Coincidence& coincidence = fCoincidences[index]; + Segment* thisOne = coincidence.fSegments[0]; + Segment* other = coincidence.fSegments[1]; + double startT = coincidence.fTs[0][0]; + double endT = coincidence.fTs[0][1]; + if (startT > endT) { + SkTSwap<double>(startT, endT); + } + SkASSERT(endT - startT >= FLT_EPSILON); + double oStartT = coincidence.fTs[1][0]; + double oEndT = coincidence.fTs[1][1]; + if (oStartT > oEndT) { + SkTSwap<double>(oStartT, oEndT); + } + SkASSERT(oEndT - oStartT >= FLT_EPSILON); + if (winding > 0 || thisOne->cancels(*other)) { + thisOne->addTCancel(startT, endT, *other, oStartT, oEndT); + } else { + thisOne->addTCoincident(startT, endT, *other, oStartT, oEndT); + } + } + } + + const SkTArray<Segment>& segments() { + return fSegments; + } + + void setWinding(int winding) { + SkASSERT(fWindingSum < 0); + fWindingSum = winding; + } + // OPTIMIZATION: feel pretty uneasy about this. It seems like once again // we need to sort and walk edges in y, but that on the surface opens the // same can of worms as before. But then, this is a rough sort based on // segments' top, and not a true sort, so it could be ameniable to regular // sorting instead of linear searching. Still feel like I'm missing something - Segment* topSegment() { + Segment* topSegment(SkScalar& bestY) { int segmentCount = fSegments.count(); SkASSERT(segmentCount > 0); int best = -1; Segment* bestSegment = NULL; while (++best < segmentCount) { Segment* testSegment = &fSegments[best]; + #if 0 // FIXME: remove if not needed + if (testSegment->virtuallyDone()) { + continue; + } + #else if (testSegment->done()) { continue; } + #endif bestSegment = testSegment; break; } if (!bestSegment) { return NULL; } - SkScalar bestTop = bestSegment->bounds().fTop; + SkScalar bestTop = bestSegment->activeTop(); for (int test = best + 1; test < segmentCount; ++test) { Segment* testSegment = &fSegments[test]; if (testSegment->done()) { continue; } - SkScalar testTop = testSegment->bounds().fTop; + if (testSegment->bounds().fTop > bestTop) { + continue; + } + SkScalar testTop = testSegment->activeTop(); if (bestTop > testTop) { bestTop = testTop; bestSegment = testSegment; } } + bestY = bestTop; return bestSegment; } + int updateSegment(int index, const SkPoint* pts) { + Segment& segment = fSegments[index]; + segment.updatePts(pts); + return segment.verb() + 1; + } + + int winding() { + if (fWindingSum >= 0) { + return fWindingSum; + } + // check peers + int count = fCrosses.count(); + for (int index = 0; index < count; ++index) { + const Contour* crosser = fCrosses[index]; + if (0 <= crosser->fWindingSum) { + fWindingSum = crosser->fWindingSum; + break; + } + } + return fWindingSum; + } + +#if DEBUG_TEST + SkTArray<Segment>& debugSegments() { + return fSegments; + } +#endif + #if DEBUG_DUMP void dump() { int i; @@ -1627,17 +2029,18 @@ protected: } fBounds = fSegments.front().bounds(); for (int index = 1; index < count; ++index) { - fBounds.growToInclude(fSegments[index].bounds()); + fBounds.add(fSegments[index].bounds()); } } -public: - SkTArray<Segment> fSegments; // not worth accessor functions? - private: + SkTArray<Segment> fSegments; + SkTDArray<Coincidence> fCoincidences; + SkTDArray<const Contour*> fCrosses; Bounds fBounds; bool fContainsIntercepts; bool fContainsCurves; + int fWindingSum; // initial winding number outside #if DEBUG_DUMP int fID; #endif @@ -1661,7 +2064,7 @@ EdgeBuilder(const SkPath& path, SkTArray<Contour>& contours) protected: void complete() { - if (fCurrentContour && fCurrentContour->fSegments.count()) { + if (fCurrentContour && fCurrentContour->segments().count()) { fCurrentContour->complete(); fCurrentContour = NULL; } @@ -1755,25 +2158,24 @@ void walk() { SkASSERT(fCurrentContour); } complete(); - if (fCurrentContour && !fCurrentContour->fSegments.count()) { + if (fCurrentContour && !fCurrentContour->segments().count()) { fContours.pop_back(); } // correct pointers in contours since fReducePts may have moved as it grew int cIndex = 0; - fCurrentContour = &fContours[0]; int extraCount = fExtra.count(); - SkASSERT(fExtra[0] == -1); + SkASSERT(extraCount == 0 || fExtra[0] == -1); int eIndex = 0; int rIndex = 0; while (++eIndex < extraCount) { int offset = fExtra[eIndex]; if (offset < 0) { - fCurrentContour = &fContours[++cIndex]; + ++cIndex; continue; } - Segment& segment = fCurrentContour->fSegments[offset - 1]; - segment.updatePts(&fReducePts[rIndex]); - rIndex += segment.verb() + 1; + fCurrentContour = &fContours[cIndex]; + rIndex += fCurrentContour->updateSegment(offset - 1, + &fReducePts[rIndex]); } fExtra.reset(); // we're done with this } @@ -1797,11 +2199,15 @@ public: kQuad_Segment = SkPath::kQuad_Verb, kCubic_Segment = SkPath::kCubic_Verb, }; + + void addCoincident(Work& other, const Intersections& ts, bool swap) { + fContour->addCoincident(fIndex, other.fContour, other.fIndex, ts, swap); + } // FIXME: does it make sense to write otherIndex now if we're going to // fix it up later? void addOtherT(int index, double otherT, int otherIndex) { - fContour->fSegments[fIndex].addOtherT(index, otherT, otherIndex); + fContour->addOtherT(fIndex, index, otherT, otherIndex); } // Avoid collapsing t values that are close to the same since @@ -1809,10 +2215,8 @@ public: // be nearly equal, any problems caused by this should be taken care // of later. // On the edge or out of range values are negative; add 2 to get end - int addT(double newT, const Work& other, int coincident) { - fContour->containsIntercepts(); - return fContour->fSegments[fIndex].addT(newT, - other.fContour->fSegments[other.fIndex], coincident); + int addT(double newT, const Work& other) { + return fContour->addT(fIndex, newT, other.fContour, other.fIndex); } bool advance() { @@ -1824,7 +2228,7 @@ public: } const Bounds& bounds() const { - return fContour->fSegments[fIndex].bounds(); + return fContour->segments()[fIndex].bounds(); } const SkPoint* cubic() const { @@ -1834,7 +2238,7 @@ public: void init(Contour* contour) { fContour = contour; fIndex = 0; - fLast = contour->fSegments.count(); + fLast = contour->segments().count(); } SkScalar left() const { @@ -1852,7 +2256,7 @@ public: } const SkPoint* pts() const { - return fContour->fSegments[fIndex].pts(); + return fContour->segments()[fIndex].pts(); } SkScalar right() const { @@ -1864,7 +2268,7 @@ public: } SegmentType segmentType() const { - const Segment& segment = fContour->fSegments[fIndex]; + const Segment& segment = fContour->segments()[fIndex]; SegmentType type = (SegmentType) segment.verb(); if (type != kLine_Segment) { return type; @@ -1888,7 +2292,7 @@ public: } SkPath::Verb verb() const { - return fContour->fSegments[fIndex].verb(); + return fContour->segments()[fIndex].verb(); } SkScalar x() const { @@ -1904,7 +2308,7 @@ public: } bool yFlipped() const { - return y() != pts()[0].fX; + return y() != pts()[0].fY; } protected: @@ -1959,6 +2363,7 @@ static bool addIntersectTs(Contour* test, Contour* next) { } Work wt; wt.init(test); + bool foundCommonContour = test == next; do { Work wn; wn.init(next); @@ -2116,70 +2521,155 @@ static bool addIntersectTs(Contour* test, Contour* next) { default: SkASSERT(0); } + if (!foundCommonContour && pts > 0) { + test->addCross(next); + next->addCross(test); + foundCommonContour = true; + } // in addition to recording T values, record matching segment - int testCoin; - int nextCoin; if (pts == 2 && wn.segmentType() <= Work::kLine_Segment && wt.segmentType() <= Work::kLine_Segment) { - // pass coincident so that smaller T is -1, larger T is 1 - testCoin = ts.fT[swap][0] < ts.fT[swap][1] ? -1 : 1; - nextCoin = ts.fT[!swap][0] < ts.fT[!swap][1] ? -1 : 1; - } else { - testCoin = nextCoin = 0; + wt.addCoincident(wn, ts, swap); + continue; } for (int pt = 0; pt < pts; ++pt) { SkASSERT(ts.fT[0][pt] >= 0 && ts.fT[0][pt] <= 1); SkASSERT(ts.fT[1][pt] >= 0 && ts.fT[1][pt] <= 1); - int testTAt = wt.addT(ts.fT[swap][pt], wn, testCoin); - int nextTAt = wn.addT(ts.fT[!swap][pt], wt, nextCoin); + int testTAt = wt.addT(ts.fT[swap][pt], wn); + int nextTAt = wn.addT(ts.fT[!swap][pt], wt); wt.addOtherT(testTAt, ts.fT[!swap][pt], nextTAt); wn.addOtherT(nextTAt, ts.fT[swap][pt], testTAt); - testCoin = -testCoin; - nextCoin = -nextCoin; } } while (wn.advance()); } while (wt.advance()); return true; } +// resolve any coincident pairs found while intersecting, and // see if coincidence is formed by clipping non-concident segments static void coincidenceCheck(SkTDArray<Contour*>& contourList, int winding) { int contourCount = contourList.count(); for (int cIndex = 0; cIndex < contourCount; ++cIndex) { Contour* contour = contourList[cIndex]; + contour->resolveCoincidence(winding); + } + for (int cIndex = 0; cIndex < contourCount; ++cIndex) { + Contour* contour = contourList[cIndex]; contour->findTooCloseToCall(winding); } } +// project a ray from the top of the contour up and see if it hits anything +// note: when we compute line intersections, we keep track of whether +// two contours touch, so we need only look at contours not touching this one. +// OPTIMIZATION: sort contourList vertically to avoid linear walk +static int innerContourCheck(SkTDArray<Contour*>& contourList, + Contour* baseContour, const SkPoint& basePt) { + int contourCount = contourList.count(); + int winding = 0; + SkScalar bestY = SK_ScalarMin; + for (int cTest = 0; cTest < contourCount; ++cTest) { + Contour* contour = contourList[cTest]; + if (basePt.fY < contour->bounds().fTop) { + continue; + } + if (bestY > contour->bounds().fBottom) { + continue; + } + if (baseContour->crosses(contour)) { + continue; + } + int tIndex; + double tHit; + const Segment* test = contour->crossedSegment(basePt, bestY, tIndex, + tHit); + if (!test) { + continue; + } + // If the ray hit the end of a span, we need to construct the wheel of + // angles to find the span closest to the ray -- even if there are just + // two spokes on the wheel. + if (tHit == test->t(tIndex)) { + SkTDArray<Angle> angles; + int end = test->nextSpan(tIndex, 1); + if (end < 0) { + end = test->nextSpan(tIndex, -1); + } + test->addTwoAngles(tIndex, end, angles); + // test->buildAnglesInner(tIndex, angles); + test->buildAngles(tIndex, angles); + SkTDArray<Angle*> sorted; + sortAngles(angles, sorted); + const Angle* angle = sorted[0]; + test = angle->segment(); + SkScalar testDx = (*SegmentDXAtT[test->verb()])(test->pts(), tHit); + if (testDx == 0) { + angle = *(sorted.end() - 1); + test = angle->segment(); + SkASSERT((*SegmentDXAtT[test->verb()])(test->pts(), tHit) != 0); + } + tIndex = angle->start(); // lesser Y + winding = test->winding(SkMin32(tIndex, angle->end())); + #if DEBUG_WINDING + SkDebugf("%s 1 winding=%d\n", __FUNCTION__, winding); + #endif + } else { + winding = test->winding(tIndex); + #if DEBUG_WINDING + SkDebugf("%s 2 winding=%d\n", __FUNCTION__, winding); + #endif + } + // see if a + change in T results in a +/- change in X (compute x'(T)) + SkScalar dx = (*SegmentDXAtT[test->verb()])(test->pts(), tHit); + #if DEBUG_WINDING + SkDebugf("%s dx=%1.9g\n", __FUNCTION__, dx); + #endif + SkASSERT(dx != 0); + if (winding * dx > 0) { // if same signs, result is negative + winding += dx > 0 ? -1 : 1; + #if DEBUG_WINDING + SkDebugf("%s 3 winding=%d\n", __FUNCTION__, winding); + #endif + } + } + baseContour->setWinding(winding); + return winding; +} // OPTIMIZATION: not crazy about linear search here to find top active y. // seems like we should break down and do the sort, or maybe sort each // contours' segments? // Once the segment array is built, there's no reason I can think of not to // sort it in Y. hmmm +// FIXME: return the contour found to pass to inner contour check static Segment* findTopContour(SkTDArray<Contour*>& contourList, - int contourCount) { + Contour*& topContour) { + int contourCount = contourList.count(); int cIndex = 0; Segment* topStart; + SkScalar bestY = SK_ScalarMax; + Contour* contour; do { - Contour* topContour = contourList[cIndex]; - topStart = topContour->topSegment(); + contour = contourList[cIndex]; + topStart = contour->topSegment(bestY); } while (!topStart && ++cIndex < contourCount); if (!topStart) { return NULL; } - SkScalar top = topStart->bounds().fTop; - for (int cTest = cIndex + 1; cTest < contourCount; ++cTest) { - Contour* contour = contourList[cTest]; - if (top < contour->bounds().fTop) { + topContour = contour; + while (++cIndex < contourCount) { + contour = contourList[cIndex]; + if (bestY < contour->bounds().fTop) { continue; } - Segment* test = contour->topSegment(); - if (top > test->bounds().fTop) { - cIndex = cTest; - topStart = test; - top = test->bounds().fTop; + SkScalar testY = SK_ScalarMax; + Segment* test = contour->topSegment(testY); + if (!test || bestY <= testY) { + continue; } + topContour = contour; + topStart = test; + bestY = testY; } return topStart; } @@ -2194,36 +2684,65 @@ static Segment* findTopContour(SkTDArray<Contour*>& contourList, // since we start with leftmost top edge, we'll traverse through a // smaller angle counterclockwise to get to the next edge. static void bridge(SkTDArray<Contour*>& contourList, SkPath& simple) { - int contourCount = contourList.count(); + // after findTopContour has already been called once, check if + // result of subsequent findTopContour has no winding set + bool firstContour = true; do { - Segment* topStart = findTopContour(contourList, contourCount); + Contour* topContour; + Segment* topStart = findTopContour(contourList, topContour); if (!topStart) { break; } - // FIXME: if this contour is inside others, winding will not be zero initially - int winding = 0; // zero says there are no contours outside this one // Start at the top. Above the top is outside, below is inside. // follow edges to intersection by changing the index by direction. int index, endIndex; Segment* current = topStart->findTop(index, endIndex); - winding += SkSign32(index - endIndex); + int winding; + int contourWinding; + if (firstContour) { + topContour->setWinding(0); + contourWinding = 0; + firstContour = false; + winding = 0; + } else { + winding = topContour->winding(); + #if DEBUG_WINDING + SkDebugf("%s 1 winding=%d\n", __FUNCTION__, winding); + #endif + if (!winding) { + const SkPoint& topPoint = current->xyAtT(endIndex); + winding = innerContourCheck(contourList, topContour, topPoint); + #if DEBUG_WINDING + SkDebugf("%s 2 winding=%d\n", __FUNCTION__, winding); + #endif + } + } const SkPoint* firstPt = NULL; SkPoint lastPt; + bool active = winding >= -1 && winding <= 1; + bool firstTime = true; + int spanWinding = current->spanSign(index, endIndex); + #if DEBUG_WINDING + SkDebugf("%s spanWinding=%d\n", __FUNCTION__, startWinding); + #endif do { SkASSERT(!current->done()); int nextStart, nextEnd; - Segment* next = current->findNext(winding, index, endIndex, - nextStart, nextEnd); + Segment* next = current->findNext(winding + spanWinding, index, + endIndex, nextStart, nextEnd, firstTime); if (!next) { break; } if (!firstPt) { - firstPt = ¤t->addMoveTo(index, simple); + firstPt = ¤t->addMoveTo(index, simple, active); } - lastPt = current->addCurveTo(index, endIndex, simple); + lastPt = current->addCurveTo(index, endIndex, simple, active); current = next; index = nextStart; endIndex = nextEnd; + spanWinding = SkSign32(spanWinding) * next->windValue( + SkMin32(nextStart, nextEnd)); + firstTime = false; } while (*firstPt != lastPt); if (firstPt) { #if DEBUG_PATH_CONSTRUCTION @@ -2232,8 +2751,6 @@ static void bridge(SkTDArray<Contour*>& contourList, SkPath& simple) { simple.close(); } } while (true); - // FIXME: more work to be done for contained (but not intersecting) - // segments } static void fixOtherTIndex(SkTDArray<Contour*>& contourList) { |