diff options
author | caryclark@google.com <caryclark@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2012-04-26 21:01:06 +0000 |
---|---|---|
committer | caryclark@google.com <caryclark@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2012-04-26 21:01:06 +0000 |
commit | fa0588ff672564af1c235a63589573829035a60b (patch) | |
tree | e62a5152abd641fa2435951327ecfe3697ce3883 /experimental | |
parent | 8e124a2454543e17e69d5f383c1a63c3d034001a (diff) |
work in progress
in the middle of switching to sortless version
git-svn-id: http://skia.googlecode.com/svn/trunk@3768 2bbb7eff-a529-9590-31e7-b0007b416f81
Diffstat (limited to 'experimental')
25 files changed, 2322 insertions, 400 deletions
diff --git a/experimental/Intersection/ActiveEdge_Test.cpp b/experimental/Intersection/ActiveEdge_Test.cpp index fe7d664e17..03cb595743 100755 --- a/experimental/Intersection/ActiveEdge_Test.cpp +++ b/experimental/Intersection/ActiveEdge_Test.cpp @@ -74,9 +74,9 @@ void ActiveEdge_Test() { right.fWorkEdge.fEdge = &rightIn; for (size_t x = 0; x < leftRightCount; ++x) { left.fAbove = leftRight[x][0]; - left.fBelow = leftRight[x][1]; + left.fTangent = left.fBelow = leftRight[x][1]; right.fAbove = leftRight[x][2]; - right.fBelow = leftRight[x][3]; + right.fTangent = right.fBelow = leftRight[x][3]; SkASSERT(left < right); SkASSERT(operator_less_than(left, right)); SkASSERT(!(right < left)); diff --git a/experimental/Intersection/CubicBounds.cpp b/experimental/Intersection/CubicBounds.cpp index 3534425886..5d21be6a9e 100644 --- a/experimental/Intersection/CubicBounds.cpp +++ b/experimental/Intersection/CubicBounds.cpp @@ -1,10 +1,63 @@ -/* - * CubicBounds.cpp - * edge - * - * Created by Cary Clark on 1/27/12. - * Copyright 2012 __MyCompanyName__. All rights reserved. - * - */ +#include "DataTypes.h" +#include "Extrema.h" +static int isBoundedByEndPoints(double a, double b, double c, double d) +{ + return (a <= b && a <= c && b <= d && c <= d) + || (a >= b && a >= c && b >= d && c >= d); +} +double leftMostT(const Cubic& cubic, double startT, double endT) { + double leftTs[2]; + _Point pt[2]; + int results = findExtrema(cubic[0].x, cubic[1].x, cubic[2].x, cubic[3].x, + leftTs); + int best = -1; + for (int index = 0; index < results; ++index) { + if (startT > leftTs[index] || leftTs[index] > endT) { + continue; + } + if (best < 0) { + best = index; + continue; + } + xy_at_t(cubic, leftTs[0], pt[0].x, pt[0].y); + xy_at_t(cubic, leftTs[1], pt[1].x, pt[1].y); + if (pt[0].x > pt[1].x) { + best = 1; + } + } + if (best >= 0) { + return leftTs[best]; + } + xy_at_t(cubic, startT, pt[0].x, pt[0].y); + xy_at_t(cubic, endT, pt[1].x, pt[1].y); + return pt[0].x <= pt[1].x ? startT : endT; +} + +void _Rect::setBounds(const Cubic& cubic) { + set(cubic[0]); + add(cubic[3]); + double tValues[4]; + int roots = 0; + if (!isBoundedByEndPoints(cubic[0].x, cubic[1].x, cubic[2].x, cubic[3].x)) { + roots = findExtrema(cubic[0].x, cubic[1].x, cubic[2].x, + cubic[3].x, tValues); + } + if (!isBoundedByEndPoints(cubic[0].y, cubic[1].y, cubic[2].y, cubic[3].y)) { + roots += findExtrema(cubic[0].y, cubic[1].y, cubic[2].y, + cubic[3].y, &tValues[roots]); + } + for (int x = 0; x < roots; ++x) { + _Point result; + xy_at_t(cubic, tValues[x], result.x, result.y); + add(result); + } +} + +void _Rect::setRawBounds(const Cubic& cubic) { + set(cubic[0]); + for (int x = 1; x < 4; ++x) { + add(cubic[x]); + } +} diff --git a/experimental/Intersection/CubicReduceOrder.cpp b/experimental/Intersection/CubicReduceOrder.cpp index fee179a5d4..5551026330 100644 --- a/experimental/Intersection/CubicReduceOrder.cpp +++ b/experimental/Intersection/CubicReduceOrder.cpp @@ -24,7 +24,7 @@ static int vertical_line(const Cubic& cubic, Cubic& reduction) { reduction[1] = cubic[3]; int smaller = reduction[1].y > reduction[0].y; int larger = smaller ^ 1; - int roots = SkFindCubicExtrema(cubic[0].y, cubic[1].y, cubic[2].y, cubic[3].y, tValues); + int roots = findExtrema(cubic[0].y, cubic[1].y, cubic[2].y, cubic[3].y, tValues); for (int index = 0; index < roots; ++index) { double yExtrema = interp_cubic_coords(&cubic[0].y, tValues[index]); if (reduction[smaller].y > yExtrema) { @@ -44,7 +44,7 @@ static int horizontal_line(const Cubic& cubic, Cubic& reduction) { reduction[1] = cubic[3]; int smaller = reduction[1].x > reduction[0].x; int larger = smaller ^ 1; - int roots = SkFindCubicExtrema(cubic[0].x, cubic[1].x, cubic[2].x, cubic[3].x, tValues); + int roots = findExtrema(cubic[0].x, cubic[1].x, cubic[2].x, cubic[3].x, tValues); for (int index = 0; index < roots; ++index) { double xExtrema = interp_cubic_coords(&cubic[0].x, tValues[index]); if (reduction[smaller].x > xExtrema) { @@ -127,9 +127,9 @@ static int check_linear(const Cubic& cubic, Cubic& reduction, double tValues[2]; int roots; if (useX) { - roots = SkFindCubicExtrema(cubic[0].x, cubic[1].x, cubic[2].x, cubic[3].x, tValues); + roots = findExtrema(cubic[0].x, cubic[1].x, cubic[2].x, cubic[3].x, tValues); } else { - roots = SkFindCubicExtrema(cubic[0].y, cubic[1].y, cubic[2].y, cubic[3].y, tValues); + roots = findExtrema(cubic[0].y, cubic[1].y, cubic[2].y, cubic[3].y, tValues); } for (index = 0; index < roots; ++index) { _Point extrema; diff --git a/experimental/Intersection/CurveIntersection.h b/experimental/Intersection/CurveIntersection.h index 1da2d9b19b..38d48902fb 100644 --- a/experimental/Intersection/CurveIntersection.h +++ b/experimental/Intersection/CurveIntersection.h @@ -6,6 +6,7 @@ class Intersections; // unit-testable utilities +double axialIntersect(const Quadratic& q1, const _Point& p, bool vert); bool bezier_clip(const Cubic& cubic1, const Cubic& cubic2, double& minT, double& maxT); bool bezier_clip(const Quadratic& q1, const Quadratic& q2, double& minT, double& maxT); void chop_at(const Cubic& src, CubicPair& dst, double t); @@ -34,11 +35,26 @@ int reduceOrder(const Quadratic& quad, Quadratic& reduction); int horizontalIntersect(const Cubic& cubic, double y, double tRange[3]); int horizontalIntersect(const Cubic& cubic, double left, double right, double y, double tRange[3]); +int horizontalIntersect(const Cubic& cubic, double left, double right, double y, + bool flipped, Intersections&); +int horizontalIntersect(const _Line& line, double left, double right, + double y, bool flipped, Intersections& ); int horizontalIntersect(const Quadratic& quad, double left, double right, double y, double tRange[2]); +int horizontalIntersect(const Quadratic& quad, double left, double right, + double y, bool flipped, Intersections& ); bool intersect(const Cubic& cubic1, const Cubic& cubic2, Intersections& ); int intersect(const Cubic& cubic, const _Line& line, double cRange[3], double lRange[3]); bool intersect(const Quadratic& q1, const Quadratic& q2, Intersections& ); bool intersect(const Quadratic& quad, const _Line& line, Intersections& ); +double leftMostT(const Cubic& , double startT, double endT); +double leftMostT(const _Line& , double startT, double endT); +double leftMostT(const Quadratic& , double startT, double endT); +int verticalIntersect(const Cubic& cubic, double top, double bottom, double x, + bool flipped, Intersections& ); +int verticalIntersect(const _Line& line, double top, double bottom, double x, + bool flipped, Intersections& ); +int verticalIntersect(const Quadratic& quad, double top, double bottom, + double x, bool flipped, Intersections& ); #endif diff --git a/experimental/Intersection/EdgeWalker.cpp b/experimental/Intersection/EdgeWalker.cpp index 1bf3c8bf50..002e84c599 100644 --- a/experimental/Intersection/EdgeWalker.cpp +++ b/experimental/Intersection/EdgeWalker.cpp @@ -1,4 +1,3 @@ - /* * Copyright 2012 Google Inc. * @@ -20,9 +19,9 @@ #define SkASSERT(cond) while (!(cond)) { sk_throw(); } // FIXME: remove once debugging is complete -#if 0 // set to 1 for no debugging whatsoever +#if 01 // set to 1 for no debugging whatsoever -const bool gRunTestsInOneThread = true; +const bool gRunTestsInOneThread = false; #define DEBUG_ACTIVE_LESS_THAN 0 #define DEBUG_ADD 0 @@ -1381,23 +1380,51 @@ struct WorkEdge { class ActiveEdge { public: // this logic must be kept in sync with tooCloseToCall - // callers expect this to only read fAbove, fBelow + // callers expect this to only read fAbove, fTangent bool operator<(const ActiveEdge& rh) const { - double topD = fAbove.fX - rh.fAbove.fX; - if (rh.fAbove.fY < fAbove.fY) { - topD = (rh.fBelow.fY - rh.fAbove.fY) * topD - - (fAbove.fY - rh.fAbove.fY) * (rh.fBelow.fX - rh.fAbove.fX); - } else if (rh.fAbove.fY > fAbove.fY) { - topD = (fBelow.fY - fAbove.fY) * topD - + (rh.fAbove.fY - fAbove.fY) * (fBelow.fX - fAbove.fX); - } - double botD = fBelow.fX - rh.fBelow.fX; - if (rh.fBelow.fY > fBelow.fY) { - botD = (rh.fBelow.fY - rh.fAbove.fY) * botD - - (fBelow.fY - rh.fBelow.fY) * (rh.fBelow.fX - rh.fAbove.fX); - } else if (rh.fBelow.fY < fBelow.fY) { - botD = (fBelow.fY - fAbove.fY) * botD - + (rh.fBelow.fY - fBelow.fY) * (fBelow.fX - fAbove.fX); + if (fVerb == rh.fVerb) { + // FIXME: don't know what to do if verb is quad, cubic + return abCompare(fAbove, fBelow, rh.fAbove, rh.fBelow); + } + // figure out which is quad, line + // if cached data says line did not intersect quad, use top/bottom + if (fVerb != SkPath::kLine_Verb ? noIntersect(rh) : rh.noIntersect(*this)) { + return abCompare(fAbove, fBelow, rh.fAbove, rh.fBelow); + } + // use whichever of top/tangent tangent/bottom overlaps more + // with line top/bot + // assumes quad/cubic can already be upconverted to cubic/cubic + const SkPoint* line[2]; + const SkPoint* curve[4]; + if (fVerb != SkPath::kLine_Verb) { + line[0] = &rh.fAbove; + line[1] = &rh.fBelow; + curve[0] = &fAbove; + curve[1] = &fTangent; + curve[2] = &fBelow; + } else { + line[0] = &fAbove; + line[1] = &fBelow; + curve[0] = &rh.fAbove; + curve[1] = &rh.fTangent; + curve[2] = &rh.fBelow; + } + + } + + bool abCompare(const SkPoint& a1, const SkPoint& a2, const SkPoint& b1, + const SkPoint& b2) const { + double topD = a1.fX - b1.fX; + if (b1.fY < a1.fY) { + topD = (b2.fY - b1.fY) * topD - (a1.fY - b1.fY) * (b2.fX - b1.fX); + } else if (b1.fY > a1.fY) { + topD = (a2.fY - a1.fY) * topD + (b1.fY - a1.fY) * (a2.fX - a1.fX); + } + double botD = a2.fX - b2.fX; + if (b2.fY > a2.fY) { + botD = (b2.fY - b1.fY) * botD - (a2.fY - b2.fY) * (b2.fX - b1.fX); + } else if (b2.fY < a2.fY) { + botD = (a2.fY - a1.fY) * botD + (b2.fY - a2.fY) * (a2.fX - a1.fX); } // return sign of greater absolute value return (fabs(topD) > fabs(botD) ? topD : botD) < 0; @@ -1477,6 +1504,37 @@ public: SkASSERT(!fExplicitTs); fTIndex = fTs->count() + 1; } + + void calcAboveBelow(double tAbove, double tBelow) { + fVerb = fWorkEdge.verb(); + switch (fVerb) { + case SkPath::kLine_Verb: + LineXYAtT(fWorkEdge.fPts, tAbove, &fAbove); + LineXYAtT(fWorkEdge.fPts, tBelow, &fTangent); + fBelow = fTangent; + break; + case SkPath::kQuad_Verb: + // FIXME: put array in struct to avoid copy? + SkPoint quad[3]; + QuadSubDivide(fWorkEdge.fPts, tAbove, tBelow, quad); + fAbove = quad[0]; + fTangent = quad[0] != quad[1] ? quad[1] : quad[2]; + fBelow = quad[2]; + break; + case SkPath::kCubic_Verb: + SkPoint cubic[3]; + CubicSubDivide(fWorkEdge.fPts, tAbove, tBelow, cubic); + fAbove = cubic[0]; + // FIXME: can't see how quad logic for how tangent is used + // extends to cubic + fTangent = cubic[0] != cubic[1] ? cubic[1] + : cubic[0] != cubic[2] ? cubic[2] : cubic[3]; + fBelow = cubic[3]; + break; + default: + SkASSERT(0); + } + } void calcLeft(SkScalar y) { // OPTIMIZE: put a kDone_Verb at the end of the verb list? @@ -1491,29 +1549,14 @@ public: } void calcLeft() { - void (*xyAtTFunc)(const SkPoint a[], double t, SkPoint* out); - switch (fWorkEdge.verb()) { - case SkPath::kLine_Verb: - xyAtTFunc = LineXYAtT; - break; - case SkPath::kQuad_Verb: - xyAtTFunc = QuadXYAtT; - break; - case SkPath::kCubic_Verb: - xyAtTFunc = CubicXYAtT; - break; - default: - SkASSERT(0); - } - // OPTIMIZATION: if fXAbove, fXBelow have already been computed - // for the fTIndex, don't do it again - // For identical x, this lets us know which edge is first. - // If both edges have T values < 1, check x at next T (fXBelow). int add = (fTIndex <= fTs->count() - fExplicitTs) - 1; double tAbove = t(fTIndex + add); - (*xyAtTFunc)(fWorkEdge.fPts, tAbove, &fAbove); double tBelow = t(fTIndex - ~add); - (*xyAtTFunc)(fWorkEdge.fPts, tBelow, &fBelow); + // OPTIMIZATION: if fAbove, fBelow have already been computed + // for the fTIndex, don't do it again + calcAboveBelow(tAbove, tBelow); + // For identical x, this lets us know which edge is first. + // If both edges have T values < 1, check x at next T (fBelow). SkASSERT(tAbove != tBelow); // FIXME: this can loop forever // need a break if we hit the end @@ -1523,13 +1566,12 @@ public: add -= 1; SkASSERT(fTIndex + add >= 0); tAbove = t(fTIndex + add); - (*xyAtTFunc)(fWorkEdge.fPts, tAbove, &fAbove); } else { add += 1; SkASSERT(fTIndex - ~add <= fTs->count() + 1); tBelow = t(fTIndex - ~add); - (*xyAtTFunc)(fWorkEdge.fPts, tBelow, &fBelow); } + calcAboveBelow(tAbove, tBelow); } fTAbove = tAbove; fTBelow = tBelow; @@ -1541,28 +1583,17 @@ public: void fixBelow() { if (fFixBelow) { - switch (fWorkEdge.verb()) { - case SkPath::kLine_Verb: - LineXYAtT(fWorkEdge.fPts, nextT(), &fBelow); - break; - case SkPath::kQuad_Verb: - QuadXYAtT(fWorkEdge.fPts, nextT(), &fBelow); - break; - case SkPath::kCubic_Verb: - CubicXYAtT(fWorkEdge.fPts, nextT(), &fBelow); - break; - default: - SkASSERT(0); - } + fTBelow = nextT(); + calcAboveBelow(fTAbove, fTBelow); fFixBelow = false; } } void init(const InEdge* edge) { fWorkEdge.init(edge); + fDone = false; initT(); fBelow.fY = SK_ScalarMin; - fDone = false; fYBottom = SK_ScalarMin; } @@ -1576,6 +1607,10 @@ public: // fTs = &fWorkEdge.fEdge->fIntercepts[fWorkEdge.verbIndex()].fTs; // but templated arrays don't allow returning a pointer to the end() element fTIndex = 0; + if (!fDone) { + fVerb = fWorkEdge.verb(); + } + SkASSERT(fVerb > SkPath::kMove_Verb); } // OPTIMIZATION: record if two edges are coincident when the are intersected @@ -1586,13 +1621,10 @@ public: if (fAbove != edge->fAbove || fBelow != edge->fBelow) { return false; } - SkPath::Verb verb = fDone ? fWorkEdge.lastVerb() : fWorkEdge.verb(); - SkPath::Verb edgeVerb = edge->fDone ? edge->fWorkEdge.lastVerb() - : edge->fWorkEdge.verb(); - if (verb != edgeVerb) { + if (fVerb != edge->fVerb) { return false; } - switch (verb) { + switch (fVerb) { case SkPath::kLine_Verb: return true; default: @@ -1607,13 +1639,18 @@ public: return fAbove == edge->fAbove && fBelow == edge->fBelow; } - SkPath::Verb lastVerb() const { - return fDone ? fWorkEdge.lastVerb() : fWorkEdge.verb(); - } +// SkPath::Verb lastVerb() const { +// return fDone ? fWorkEdge.lastVerb() : fWorkEdge.verb(); +// } const SkPoint* lastPoints() const { return fDone ? fWorkEdge.lastPoints() : fWorkEdge.points(); } + + bool noIntersect(const ActiveEdge& ) const { + // incomplete + return false; + } // The shortest close call edge should be moved into a position where // it contributes if the winding is transitioning to or from zero. @@ -1654,8 +1691,8 @@ public: } bool swapUnordered(const ActiveEdge* edge, SkScalar bottom) const { - SkASSERT(lastVerb() != SkPath::kLine_Verb - || edge->lastVerb() != SkPath::kLine_Verb); + SkASSERT(fVerb != SkPath::kLine_Verb + || edge->fVerb != SkPath::kLine_Verb); if (fDone || edge->fDone) { return false; } @@ -1670,24 +1707,24 @@ public: double t1, t2, b1, b2; // This logic must be kept in sync with operator < if (edge->fAbove.fY < fAbove.fY) { - t1 = (edge->fBelow.fY - edge->fAbove.fY) * (fAbove.fX - edge->fAbove.fX); - t2 = (fAbove.fY - edge->fAbove.fY) * (edge->fBelow.fX - edge->fAbove.fX); + t1 = (edge->fTangent.fY - edge->fAbove.fY) * (fAbove.fX - edge->fAbove.fX); + t2 = (fAbove.fY - edge->fAbove.fY) * (edge->fTangent.fX - edge->fAbove.fX); } else if (edge->fAbove.fY > fAbove.fY) { - t1 = (fBelow.fY - fAbove.fY) * (fAbove.fX - edge->fAbove.fX); - t2 = (fAbove.fY - edge->fAbove.fY) * (fBelow.fX - fAbove.fX); + t1 = (fTangent.fY - fAbove.fY) * (fAbove.fX - edge->fAbove.fX); + t2 = (fAbove.fY - edge->fAbove.fY) * (fTangent.fX - fAbove.fX); } else { t1 = fAbove.fX; t2 = edge->fAbove.fX; } - if (edge->fBelow.fY > fBelow.fY) { - b1 = (edge->fBelow.fY - edge->fAbove.fY) * (fBelow.fX - edge->fBelow.fX); - b2 = (fBelow.fY - edge->fBelow.fY) * (edge->fBelow.fX - edge->fAbove.fX); - } else if (edge->fBelow.fY < fBelow.fY) { - b1 = (fBelow.fY - fAbove.fY) * (fBelow.fX - edge->fBelow.fX); - b2 = (fBelow.fY - edge->fBelow.fY) * (fBelow.fX - fAbove.fX); + if (edge->fTangent.fY > fTangent.fY) { + b1 = (edge->fTangent.fY - edge->fAbove.fY) * (fTangent.fX - edge->fTangent.fX); + b2 = (fTangent.fY - edge->fTangent.fY) * (edge->fTangent.fX - edge->fAbove.fX); + } else if (edge->fTangent.fY < fTangent.fY) { + b1 = (fTangent.fY - fAbove.fY) * (fTangent.fX - edge->fTangent.fX); + b2 = (fTangent.fY - edge->fTangent.fY) * (fTangent.fX - fAbove.fX); } else { - b1 = fBelow.fX; - b2 = edge->fBelow.fX; + b1 = fTangent.fX; + b2 = edge->fTangent.fX; } if (fabs(t1 - t2) > fabs(b1 - b2)) { ulps = UlpsDiff(t1, t2); @@ -1701,32 +1738,30 @@ public: if (ulps < 0 || ulps > 32) { return false; } - SkPath::Verb verb = lastVerb(); - SkPath::Verb edgeVerb = edge->lastVerb(); - if (verb == SkPath::kLine_Verb && edgeVerb == SkPath::kLine_Verb) { + if (fVerb == SkPath::kLine_Verb && edge->fVerb == SkPath::kLine_Verb) { return true; } - if (verb != SkPath::kLine_Verb && edgeVerb != SkPath::kLine_Verb) { + if (fVerb != SkPath::kLine_Verb && edge->fVerb != SkPath::kLine_Verb) { return false; } double ts[2]; bool isLine = true; bool curveQuad = true; - if (verb == SkPath::kCubic_Verb) { + if (fVerb == SkPath::kCubic_Verb) { ts[0] = (fTAbove * 2 + fTBelow) / 3; ts[1] = (fTAbove + fTBelow * 2) / 3; curveQuad = isLine = false; - } else if (edgeVerb == SkPath::kCubic_Verb) { + } else if (edge->fVerb == SkPath::kCubic_Verb) { ts[0] = (edge->fTAbove * 2 + edge->fTBelow) / 3; ts[1] = (edge->fTAbove + edge->fTBelow * 2) / 3; curveQuad = false; - } else if (verb == SkPath::kQuad_Verb) { + } else if (fVerb == SkPath::kQuad_Verb) { ts[0] = fTAbove; ts[1] = (fTAbove + fTBelow) / 2; isLine = false; } else { - SkASSERT(edgeVerb == SkPath::kQuad_Verb); + SkASSERT(edge->fVerb == SkPath::kQuad_Verb); ts[0] = edge->fTAbove; ts[1] = (edge->fTAbove + edge->fTBelow) / 2; } @@ -1775,10 +1810,10 @@ private: // utility used only by swapUnordered void extractAboveBelow(ActiveEdge& extracted) const { SkPoint curve[4]; - switch (lastVerb()) { + switch (fVerb) { case SkPath::kLine_Verb: extracted.fAbove = fAbove; - extracted.fBelow = fBelow; + extracted.fTangent = fTangent; return; case SkPath::kQuad_Verb: QuadSubDivide(lastPoints(), fTAbove, fTBelow, curve); @@ -1790,19 +1825,21 @@ private: SkASSERT(0); } extracted.fAbove = curve[0]; - extracted.fBelow = curve[1]; + extracted.fTangent = curve[1]; } public: WorkEdge fWorkEdge; const SkTDArray<double>* fTs; SkPoint fAbove; + SkPoint fTangent; SkPoint fBelow; double fTAbove; // OPTIMIZATION: only required if edge has quads or cubics double fTBelow; SkScalar fYBottom; int fCoincident; int fTIndex; + SkPath::Verb fVerb; bool fSkip; // OPTIMIZATION: use bitfields? bool fCloseCall; bool fDone; @@ -2253,17 +2290,16 @@ static SkScalar adjustCoincident(SkTDArray<ActiveEdge*>& edgeList, for (index = 1; index < edgeCount; ++index) { activePtr = nextPtr; nextPtr = edgeList[index]; - if (firstIndex != index - 1 && !activePtr->fSkip) { - if (nextPtr->fWorkEdge.verb() == SkPath::kLine_Verb - && activePtr->isUnordered(nextPtr)) { - start here; - // swap the line with the curve - // back up to the previous edge and retest - SkTSwap<ActiveEdge*>(edgeList[index - 1], edgeList[index]); - SkASSERT(index > 1); - index -= 2; - continue; - } + if (firstIndex != index - 1 && activePtr->fVerb > SkPath::kLine_Verb + && nextPtr->fVerb == SkPath::kLine_Verb + && activePtr->isUnordered(nextPtr)) { + // swap the line with the curve + // back up to the previous edge and retest + SkTSwap<ActiveEdge*>(edgeList[index - 1], edgeList[index]); + SkASSERT(index > 1); + index -= 2; + nextPtr = edgeList[index]; + continue; } bool closeCall = false; activePtr->fCoincident = firstIndex; @@ -2444,9 +2480,9 @@ static void stitchEdge(SkTDArray<ActiveEdge*>& edgeList, SkScalar y, bool moreToDo, aboveBottom; do { double currentT = activePtr->t(); - uint8_t verb = wt.verb(); const SkPoint* points = wt.fPts; double nextT; + SkPath::Verb verb = activePtr->fVerb; do { nextT = activePtr->nextT(); // FIXME: obtuse: want efficient way to say @@ -2503,9 +2539,10 @@ static void stitchEdge(SkTDArray<ActiveEdge*>& edgeList, SkScalar y, // will use these values if they're still valid instead of // recomputing if (clipped[verb].fY > activePtr->fBelow.fY - && bottom >= activePtr->fBelow.fY ) { + && bottom >= activePtr->fBelow.fY + && verb == SkPath::kLine_Verb) { activePtr->fAbove = activePtr->fBelow; - activePtr->fBelow = clipped[verb]; + activePtr->fBelow = activePtr->fTangent = clipped[verb]; activePtr->fTAbove = activePtr->fTBelow < 1 ? activePtr->fTBelow : 0; activePtr->fTBelow = nextT; @@ -2609,6 +2646,17 @@ void simplify(const SkPath& path, bool asFill, SkPath& simple) { // if a quadratic or cubic now has an intermediate T value, see if the Ts // on either side cause the Y values to monotonically increase. If not, split // the curve at the new T. + + // try an alternate approach which does not split curves or stitch edges + // (may still need adjustCoincident, though) + // the idea is to output non-intersecting contours, then figure out their + // respective winding contribution + // each contour will need to know whether it is CW or CCW, and then whether + // a ray from that contour hits any a contour that contains it. The ray can + // move to the left and then arbitrarily move up or down (as long as it never + // moves to the right) to find a reference sibling contour or containing + // contour. If the contour is part of an intersection, the companion contour + // that is part of the intersection can determine the containership. if (builder.containsCurves()) { currentPtr = edgeList.begin(); SkTArray<InEdge> splits; diff --git a/experimental/Intersection/EdgeWalkerQuadratic4x4_Test.cpp b/experimental/Intersection/EdgeWalkerQuadratic4x4_Test.cpp new file mode 100644 index 0000000000..75fd9a448d --- /dev/null +++ b/experimental/Intersection/EdgeWalkerQuadratic4x4_Test.cpp @@ -0,0 +1,101 @@ +#include "EdgeWalker_Test.h" +#include "Intersection_Tests.h" +#include "SkBitmap.h" +#include "SkCanvas.h" +#include <assert.h> + + +static void* testSimplify4x4QuadraticsMain(void* data) +{ + char pathStr[1024]; + bzero(pathStr, sizeof(pathStr)); + SkASSERT(data); + State4& state = *(State4*) data; + int ax = state.a & 0x03; + int ay = state.a >> 2; + int bx = state.b & 0x03; + int by = state.b >> 2; + int cx = state.c & 0x03; + int cy = state.c >> 2; + int dx = state.d & 0x03; + int dy = state.d >> 2; + for (int e = 0 ; e < 16; ++e) { + int ex = e & 0x03; + int ey = e >> 2; + for (int f = e ; f < 16; ++f) { + int fx = f & 0x03; + int fy = f >> 2; + for (int g = f ; g < 16; ++g) { + int gx = g & 0x03; + int gy = g >> 2; + for (int h = g ; h < 16; ++h) { + int hx = h & 0x03; + int hy = h >> 2; + SkPath path, out; + path.setFillType(SkPath::kWinding_FillType); + path.moveTo(ax, ay); + path.quadTo(bx, by, cx, cy); + path.lineTo(dx, dy); + path.close(); + path.moveTo(ex, ey); + path.lineTo(fx, fy); + path.quadTo(gx, gy, hx, hy); + path.close(); + if (1) { // gdb: set print elements 400 + char* str = pathStr; + str += sprintf(str, " path.moveTo(%d, %d);\n", ax, ay); + str += sprintf(str, " path.quadTo(%d, %d, %d, %d);\n", bx, by, cx, cy); + str += sprintf(str, " path.lineTo(%d, %d);\n", dx, dy); + str += sprintf(str, " path.close();\n"); + str += sprintf(str, " path.moveTo(%d, %d);\n", ex, ey); + str += sprintf(str, " path.lineTo(%d, %d);\n", fx, fy); + str += sprintf(str, " path.quadTo(%d, %d, %d, %d);\n", gx, gy, hx, hy); + str += sprintf(str, " path.close();"); + } + if (!testSimplify(path, true, out, state.bitmap, state.canvas)) { + SkDebugf("*/\n{ SkPath::kWinding_FillType, %d, %d, %d, %d," + " %d, %d, %d, %d },\n/*\n", state.a, state.b, state.c, state.d, + e, f, g, h); + } + path.setFillType(SkPath::kEvenOdd_FillType); + if (!testSimplify(path, true, out, state.bitmap, state.canvas)) { + SkDebugf("*/\n{ SkPath::kEvenOdd_FillType, %d, %d, %d, %d," + " %d, %d, %d, %d },\n/*\n", state.a, state.b, state.c, state.d, + e, f, g, h); + } + } + } + } + } + return NULL; +} + +const int maxThreads = gRunTestsInOneThread ? 1 : 24; + +void Simplify4x4QuadraticsThreaded_Test() +{ + State4 threadState[maxThreads]; + int threadIndex = 0; + for (int a = 0; a < 16; ++a) { + for (int b = a ; b < 16; ++b) { + for (int c = b ; c < 16; ++c) { + for (int d = c; d < 16; ++d) { + State4* statePtr = &threadState[threadIndex]; + statePtr->a = a; + statePtr->b = b; + statePtr->c = c; + statePtr->d = d; + if (maxThreads > 1) { + createThread(statePtr, testSimplify4x4QuadraticsMain); + if (++threadIndex >= maxThreads) { + waitForCompletion(threadState, threadIndex); + } + } else { + testSimplify4x4QuadraticsMain(statePtr); + } + } + } + } + } + waitForCompletion(threadState, threadIndex); +} diff --git a/experimental/Intersection/EdgeWalkerQuadratics_Test.cpp b/experimental/Intersection/EdgeWalkerQuadratics_Test.cpp index 96ba2f3c5f..f6bf8240f5 100644 --- a/experimental/Intersection/EdgeWalkerQuadratics_Test.cpp +++ b/experimental/Intersection/EdgeWalkerQuadratics_Test.cpp @@ -218,7 +218,22 @@ static void testSimplifyQuadratic16() { drawAsciiPaths(path, out, true); } +static void testSimplifyQuadratic17() { + SkPath path, out; + path.moveTo(0, 0); + path.quadTo(0, 0, 0, 0); + path.lineTo(2, 2); + path.close(); + path.moveTo(0, 1); + path.lineTo(0, 1); + path.quadTo(2, 1, 3, 3); + path.close(); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +} + static void (*simplifyTests[])() = { + testSimplifyQuadratic17, testSimplifyQuadratic16, testSimplifyQuadratic15, testSimplifyQuadratic14, @@ -239,7 +254,7 @@ static void (*simplifyTests[])() = { static size_t simplifyTestsCount = sizeof(simplifyTests) / sizeof(simplifyTests[0]); -static void (*firstTest)() = 0; +static void (*firstTest)() = testSimplifyQuadratic14; static bool skipAll = false; void SimplifyQuadraticPaths_Test() { diff --git a/experimental/Intersection/Extrema.cpp b/experimental/Intersection/Extrema.cpp index 0b850607d4..386f09225a 100644 --- a/experimental/Intersection/Extrema.cpp +++ b/experimental/Intersection/Extrema.cpp @@ -1,20 +1,18 @@ #include "DataTypes.h" #include "Extrema.h" -static int valid_unit_divide(double numer, double denom, double* ratio) +static int validUnitDivide(double numer, double denom, double* ratio) { - if (numer < 0) - { + if (numer < 0) { numer = -numer; denom = -denom; } - if (denom == 0 || numer == 0 || numer >= denom) return 0; - double r = numer / denom; - if (r == 0) // catch underflow if numer <<<< denom + if (r == 0) { // catch underflow if numer <<<< denom return 0; + } *ratio = r; return 1; } @@ -25,10 +23,10 @@ static int valid_unit_divide(double numer, double denom, double* ratio) x1 = Q / A x2 = C / Q */ -static int SkFindUnitQuadRoots(double A, double B, double C, double roots[2]) +static int findUnitQuadRoots(double A, double B, double C, double roots[2]) { if (A == 0) - return valid_unit_divide(-C, B, roots); + return validUnitDivide(-C, B, roots); double* r = roots; @@ -39,8 +37,8 @@ static int SkFindUnitQuadRoots(double A, double B, double C, double roots[2]) R = sqrt(R); double Q = (B < 0) ? -(B-R)/2 : -(B+R)/2; - r += valid_unit_divide(Q, A, r); - r += valid_unit_divide(C, Q, r); + r += validUnitDivide(Q, A, r); + r += validUnitDivide(C, Q, r); if (r - roots == 2 && approximately_equal(roots[0], roots[1])) { // nearly-equal? r -= 1; // skip the double root } @@ -51,16 +49,16 @@ static int SkFindUnitQuadRoots(double A, double B, double C, double roots[2]) A = 3(-a + 3(b - c) + d) B = 6(a - 2b + c) C = 3(b - a) - Solve for t, keeping only those that fit betwee 0 < t < 1 + Solve for t, keeping only those that fit between 0 < t < 1 */ -int SkFindCubicExtrema(double a, double b, double c, double d, double tValues[2]) +int findExtrema(double a, double b, double c, double d, double tValues[2]) { // we divide A,B,C by 3 to simplify double A = d - a + 3*(b - c); double B = 2*(a - b - b + c); double C = b - a; - return SkFindUnitQuadRoots(A, B, C, tValues); + return findUnitQuadRoots(A, B, C, tValues); } /** Quad'(t) = At + B, where @@ -68,10 +66,10 @@ int SkFindCubicExtrema(double a, double b, double c, double d, double tValues[2] B = 2(b - a) Solve for t, only if it fits between 0 < t < 1 */ -int SkFindQuadExtrema(double a, double b, double c, double tValue[1]) +int findExtrema(double a, double b, double c, double tValue[1]) { /* At + B == 0 t = -B / A */ - return valid_unit_divide(a - b, a - b - b + c, tValue); + return validUnitDivide(a - b, a - b - b + c, tValue); } diff --git a/experimental/Intersection/Extrema.h b/experimental/Intersection/Extrema.h index aae422ea71..0988540957 100644 --- a/experimental/Intersection/Extrema.h +++ b/experimental/Intersection/Extrema.h @@ -1,2 +1,2 @@ -int SkFindCubicExtrema(double a, double b, double c, double d, double tValues[2]); -int SkFindQuadExtrema(double a, double b, double c, double tValue[1]); +int findExtrema(double a, double b, double c, double d, double tValues[2]); +int findExtrema(double a, double b, double c, double tValue[1]); diff --git a/experimental/Intersection/Intersection_Tests.cpp b/experimental/Intersection/Intersection_Tests.cpp index 0179cb7450..e3c042cf27 100644 --- a/experimental/Intersection/Intersection_Tests.cpp +++ b/experimental/Intersection/Intersection_Tests.cpp @@ -4,12 +4,10 @@ void cubecode_test(int test); void testSimplify(); -#define TEST_QUADS_FIRST 01 +#define TEST_QUADS_FIRST 0 void Intersection_Tests() { -#if !TEST_QUADS_FIRST - ActiveEdge_Test(); -#endif + SimplifyAddIntersectingTs_Test(); cubecode_test(1); convert_testx(); @@ -29,8 +27,8 @@ void Intersection_Tests() { SimplifyRectangularPaths_Test(); SimplifyQuadralateralPaths_Test(); -#if TEST_QUADS_FIRST ActiveEdge_Test(); +#if TEST_QUADS_FIRST Simplify4x4QuadraticsThreaded_Test(); #endif SimplifyDegenerate4x4TrianglesThreaded_Test(); diff --git a/experimental/Intersection/Intersection_Tests.h b/experimental/Intersection/Intersection_Tests.h index e0d892df8c..a2d9df6807 100644 --- a/experimental/Intersection/Intersection_Tests.h +++ b/experimental/Intersection/Intersection_Tests.h @@ -12,6 +12,7 @@ void LineCubicIntersection_Test(); void LineIntersection_Test(); void LineParameter_Test(); void LineQuadraticIntersection_Test(); +void SimplifyAddIntersectingTs_Test(); void SimplifyDegenerate4x4TrianglesThreaded_Test(); void SimplifyNondegenerate4x4TrianglesThreaded_Test(); void SimplifyPolygonPaths_Test(); diff --git a/experimental/Intersection/LineCubicIntersection.cpp b/experimental/Intersection/LineCubicIntersection.cpp index 8517b7e0f8..6f2cf6c5e5 100644 --- a/experimental/Intersection/LineCubicIntersection.cpp +++ b/experimental/Intersection/LineCubicIntersection.cpp @@ -109,6 +109,13 @@ int horizontalIntersect(double axisIntercept) { return cubicRoots(A, B, C, D, range); } +int verticalIntersect(double axisIntercept) { + double A, B, C, D; + coefficients(&cubic[0].x, A, B, C, D); + D -= axisIntercept; + return cubicRoots(A, B, C, D, range); +} + double findLineT(double t) { const double* cPtr; const double* lPtr; @@ -158,6 +165,56 @@ int horizontalIntersect(const Cubic& cubic, double left, double right, double y, return result; } +int horizontalIntersect(const Cubic& cubic, double left, double right, double y, + bool flipped, Intersections& intersections) { + LineCubicIntersections c(cubic, *((_Line*) 0), intersections.fT[0]); + int result = c.horizontalIntersect(y); + for (int index = 0; index < result; ) { + double x, y; + xy_at_t(cubic, intersections.fT[0][index], x, y); + if (x < left || x > right) { + if (--result > index) { + intersections.fT[0][index] = intersections.fT[0][result]; + } + continue; + } + intersections.fT[0][index] = (x - left) / (right - left); + ++index; + } + if (flipped) { + // OPTIMIZATION: instead of swapping, pass original line, use [1].x - [0].x + for (int index = 0; index < result; ++index) { + intersections.fT[1][index] = 1 - intersections.fT[1][index]; + } + } + return result; +} + +int verticalIntersect(const Cubic& cubic, double top, double bottom, double x, + bool flipped, Intersections& intersections) { + LineCubicIntersections c(cubic, *((_Line*) 0), intersections.fT[0]); + int result = c.verticalIntersect(x); + for (int index = 0; index < result; ) { + double x, y; + xy_at_t(cubic, intersections.fT[0][index], x, y); + if (y < top || y > bottom) { + if (--result > index) { + intersections.fT[0][index] = intersections.fT[0][result]; + } + continue; + } + intersections.fT[0][index] = (y - top) / (bottom - top); + ++index; + } + if (flipped) { + // OPTIMIZATION: instead of swapping, pass original line, use [1].x - [0].x + for (int index = 0; index < result; ++index) { + intersections.fT[1][index] = 1 - intersections.fT[1][index]; + } + } + return result; +} + int intersect(const Cubic& cubic, const _Line& line, double cRange[3], double lRange[3]) { LineCubicIntersections c(cubic, line, cRange); int roots; diff --git a/experimental/Intersection/LineIntersection.cpp b/experimental/Intersection/LineIntersection.cpp index f5128eab2d..40e046cff5 100644 --- a/experimental/Intersection/LineIntersection.cpp +++ b/experimental/Intersection/LineIntersection.cpp @@ -1,4 +1,5 @@ #include "DataTypes.h" +#include "Intersections.h" #include "LineIntersection.h" #include <algorithm> // used for std::swap @@ -112,9 +113,9 @@ int horizontalLineIntersect(const _Line& line, double left, double right, double y, double tRange[2]) { int result = horizontalIntersect(line, y, tRange); if (result != 1) { + // FIXME: this is incorrect if result == 2 return result; } - // FIXME: this is incorrect if result == 2 double xIntercept = line[0].x + tRange[0] * (line[1].x - line[0].x); if (xIntercept > right || xIntercept < left) { return 0; @@ -122,6 +123,116 @@ int horizontalLineIntersect(const _Line& line, double left, double right, return result; } +int horizontalIntersect(const _Line& line, double left, double right, + double y, bool flipped, Intersections& intersections) { + int result = horizontalIntersect(line, y, intersections.fT[0]); + switch (result) { + case 0: + break; + case 1: { + double xIntercept = line[0].x + intersections.fT[0][0] + * (line[1].x - line[0].x); + if (xIntercept > right || xIntercept < left) { + return 0; + } + intersections.fT[1][0] = (xIntercept - left) / (right - left); + break; + } + case 2: + double lineL = line[0].x; + double lineR = line[1].x; + if (lineL > lineR) { + std::swap(lineL, lineR); + } + double overlapL = std::max(left, lineL); + double overlapR = std::min(right, lineR); + if (overlapL > overlapR) { + return 0; + } + if (overlapL == overlapR) { + result = 1; + } + intersections.fT[0][0] = (overlapL - line[0].x) / (line[1].x - line[0].x); + intersections.fT[1][0] = (overlapL - left) / (right - left); + if (result > 1) { + intersections.fT[0][1] = (overlapR - line[0].x) / (line[1].x - line[0].x); + intersections.fT[1][1] = (overlapR - left) / (right - left); + } + break; + } + if (flipped) { + // OPTIMIZATION: instead of swapping, pass original line, use [1].x - [0].x + for (int index = 0; index < result; ++index) { + intersections.fT[1][index] = 1 - intersections.fT[1][index]; + } + } + return result; +} + +int verticalIntersect(const _Line& line, double x, double tRange[2]) { + double min = line[0].x; + double max = line[1].x; + if (min > max) { + std::swap(min, max); + } + if (min > x || max < x) { + return 0; + } + if (approximately_equal(min, max)) { + tRange[0] = 0; + tRange[1] = 1; + return 2; + } + tRange[0] = (x - line[0].x) / (line[1].x - line[0].x); + return 1; +} + +int verticalIntersect(const _Line& line, double top, double bottom, + double x, bool flipped, Intersections& intersections) { + int result = verticalIntersect(line, x, intersections.fT[0]); + switch (result) { + case 0: + break; + case 1: { + double yIntercept = line[0].y + intersections.fT[0][0] + * (line[1].y - line[0].y); + if (yIntercept > bottom || yIntercept < top) { + return 0; + } + intersections.fT[1][0] = (yIntercept - top) / (bottom - top); + break; + } + case 2: + double lineT = line[0].y; + double lineB = line[1].y; + if (lineT > lineB) { + std::swap(lineT, lineB); + } + double overlapT = std::max(top, lineT); + double overlapB = std::min(bottom, lineB); + if (overlapT > overlapB) { + return 0; + } + if (overlapT == overlapB) { + result = 1; + } + intersections.fT[0][0] = (overlapT - line[0].y) / (line[1].y - line[0].y); + intersections.fT[1][0] = (overlapT - top) / (bottom - top); + if (result > 1) { + intersections.fT[0][1] = (overlapB - line[0].y) / (line[1].y - line[0].y); + intersections.fT[1][1] = (overlapB - top) / (bottom - top); + } + break; + } + if (flipped) { + // OPTIMIZATION: instead of swapping, pass original line, use [1].y - [0].y + for (int index = 0; index < result; ++index) { + intersections.fT[1][index] = 1 - intersections.fT[1][index]; + } + } + return result; +} + // from http://www.bryceboe.com/wordpress/wp-content/uploads/2006/10/intersect.py // 4 subs, 2 muls, 1 cmp static bool ccw(const _Point& A, const _Point& B, const _Point& C) { diff --git a/experimental/Intersection/LineIntersection.h b/experimental/Intersection/LineIntersection.h index bb8b4b7d6f..d88e1f4653 100644 --- a/experimental/Intersection/LineIntersection.h +++ b/experimental/Intersection/LineIntersection.h @@ -6,6 +6,8 @@ int horizontalIntersect(const _Line& line, double y, double tRange[2]); int horizontalLineIntersect(const _Line& line, double left, double right, double y, double tRange[2]); +int verticalLineIntersect(const _Line& line, double top, double bottom, + double x, double tRange[2]); int intersect(const _Line& a, const _Line& b, double aRange[2], double bRange[2]); bool testIntersect(const _Line& a, const _Line& b); diff --git a/experimental/Intersection/LineQuadraticIntersection.cpp b/experimental/Intersection/LineQuadraticIntersection.cpp index 737b76749d..1bc831b643 100644 --- a/experimental/Intersection/LineQuadraticIntersection.cpp +++ b/experimental/Intersection/LineQuadraticIntersection.cpp @@ -141,6 +141,16 @@ int horizontalIntersect(double axisIntercept) { return quadraticRoots(D, E, F, intersections.fT[0]); } +int verticalIntersect(double axisIntercept) { + double D = quad[2].x; // f + double E = quad[1].x; // e + double F = quad[0].x; // d + D += F - 2 * E; // D = d - 2*e + f + E -= F; // E = -(d - e) + F -= axisIntercept; + return quadraticRoots(D, E, F, intersections.fT[0]); +} + protected: double findLineT(double t) { @@ -167,6 +177,46 @@ bool moreHorizontal; }; +// utility for pairs of coincident quads +static double horizontalIntersect(const Quadratic& quad, const _Point& pt) { + Intersections intersections; + LineQuadraticIntersections q(quad, *((_Line*) 0), intersections); + int result = q.horizontalIntersect(pt.y); + if (result == 0) { + return -1; + } + assert(result == 1); + double x, y; + xy_at_t(quad, intersections.fT[0][0], x, y); + if (approximately_equal(x, pt.x)) { + return intersections.fT[0][0]; + } + return -1; +} + +static double verticalIntersect(const Quadratic& quad, const _Point& pt) { + Intersections intersections; + LineQuadraticIntersections q(quad, *((_Line*) 0), intersections); + int result = q.horizontalIntersect(pt.x); + if (result == 0) { + return -1; + } + assert(result == 1); + double x, y; + xy_at_t(quad, intersections.fT[0][0], x, y); + if (approximately_equal(y, pt.y)) { + return intersections.fT[0][0]; + } + return -1; +} + +double axialIntersect(const Quadratic& q1, const _Point& p, bool vertical) { + if (vertical) { + return verticalIntersect(q1, p); + } + return horizontalIntersect(q1, p); +} + int horizontalIntersect(const Quadratic& quad, double left, double right, double y, double tRange[2]) { Intersections i; @@ -184,6 +234,56 @@ int horizontalIntersect(const Quadratic& quad, double left, double right, return tCount; } +int horizontalIntersect(const Quadratic& quad, double left, double right, double y, + bool flipped, Intersections& intersections) { + LineQuadraticIntersections q(quad, *((_Line*) 0), intersections); + int result = q.horizontalIntersect(y); + for (int index = 0; index < result; ) { + double x, y; + xy_at_t(quad, intersections.fT[0][index], x, y); + if (x < left || x > right) { + if (--result > index) { + intersections.fT[0][index] = intersections.fT[0][result]; + } + continue; + } + intersections.fT[0][index] = (x - left) / (right - left); + ++index; + } + if (flipped) { + // OPTIMIZATION: instead of swapping, pass original line, use [1].x - [0].x + for (int index = 0; index < result; ++index) { + intersections.fT[1][index] = 1 - intersections.fT[1][index]; + } + } + return result; +} + +int verticalIntersect(const Quadratic& quad, double top, double bottom, double x, + bool flipped, Intersections& intersections) { + LineQuadraticIntersections q(quad, *((_Line*) 0), intersections); + int result = q.verticalIntersect(x); + for (int index = 0; index < result; ) { + double x, y; + xy_at_t(quad, intersections.fT[0][index], x, y); + if (y < top || y > bottom) { + if (--result > index) { + intersections.fT[0][index] = intersections.fT[0][result]; + } + continue; + } + intersections.fT[0][index] = (y - top) / (bottom - top); + ++index; + } + if (flipped) { + // OPTIMIZATION: instead of swapping, pass original line, use [1].x - [0].x + for (int index = 0; index < result; ++index) { + intersections.fT[1][index] = 1 - intersections.fT[1][index]; + } + } + return result; +} + bool intersect(const Quadratic& quad, const _Line& line, Intersections& i) { LineQuadraticIntersections q(quad, line, i); return q.intersect(); diff --git a/experimental/Intersection/LineQuadraticIntersection_Test.cpp b/experimental/Intersection/LineQuadraticIntersection_Test.cpp index 017a44ed77..a48f0af128 100644 --- a/experimental/Intersection/LineQuadraticIntersection_Test.cpp +++ b/experimental/Intersection/LineQuadraticIntersection_Test.cpp @@ -8,7 +8,9 @@ struct lineQuad { Quadratic quad; _Line line; } lineQuadTests[] = { - {{{0, 0}, {0, 1}, {1, 1}}, {{0, 1}, {1, 0}}} + {{{2, 0}, {1, 1}, {2, 2}}, {{0, 0}, {0, 2}}}, + {{{4, 0}, {0, 1}, {4, 2}}, {{3, 1}, {4, 1}}}, + {{{0, 0}, {0, 1}, {1, 1}}, {{0, 1}, {1, 0}}}, }; size_t lineQuadTests_count = sizeof(lineQuadTests) / sizeof(lineQuadTests[0]); diff --git a/experimental/Intersection/LineUtilities.cpp b/experimental/Intersection/LineUtilities.cpp index 13942252d9..38e1ba0c1f 100644 --- a/experimental/Intersection/LineUtilities.cpp +++ b/experimental/Intersection/LineUtilities.cpp @@ -30,3 +30,28 @@ void sub_divide(const _Line& line, double t1, double t2, _Line& dst) { dst[1].x = line[0].x - t2 * delta.x; dst[1].y = line[0].y - t2 * delta.y; } + +// may have this below somewhere else already: +// copying here because I thought it was clever + +// Copyright 2001, softSurfer (www.softsurfer.com) +// This code may be freely used and modified for any purpose +// providing that this copyright notice is included with it. +// SoftSurfer makes no warranty for this code, and cannot be held +// liable for any real or imagined damage resulting from its use. +// Users of this code must verify correctness for their application. + +// Assume that a class is already given for the object: +// Point with coordinates {float x, y;} +//=================================================================== + +// isLeft(): tests if a point is Left|On|Right of an infinite line. +// Input: three points P0, P1, and P2 +// Return: >0 for P2 left of the line through P0 and P1 +// =0 for P2 on the line +// <0 for P2 right of the line +// See: the January 2001 Algorithm on Area of Triangles +float isLeft( _Point P0, _Point P1, _Point P2 ) +{ + return (P1.x - P0.x)*(P2.y - P0.y) - (P2.x - P0.x)*(P1.y - P0.y); +} diff --git a/experimental/Intersection/QuadraticBounds.cpp b/experimental/Intersection/QuadraticBounds.cpp new file mode 100644 index 0000000000..11591b3c17 --- /dev/null +++ b/experimental/Intersection/QuadraticBounds.cpp @@ -0,0 +1,46 @@ +#include "DataTypes.h" +#include "Extrema.h" + +static int isBoundedByEndPoints(double a, double b, double c) +{ + return (a <= b && b <= c) || (a >= b && b >= c); +} + +double leftMostT(const Quadratic& quad, double startT, double endT) { + double leftT; + if (findExtrema(quad[0].x, quad[1].x, quad[2].x, &leftT) + && startT <= leftT && leftT <= endT) { + return leftT; + } + _Point startPt; + xy_at_t(quad, startT, startPt.x, startPt.y); + _Point endPt; + xy_at_t(quad, endT, endPt.x, endPt.y); + return startPt.x <= endPt.x ? startT : endT; +} + +void _Rect::setBounds(const Quadratic& quad) { + set(quad[0]); + add(quad[2]); + double tValues[2]; + int roots = 0; + if (!isBoundedByEndPoints(quad[0].x, quad[1].x, quad[2].x)) { + roots = findExtrema(quad[0].x, quad[1].x, quad[2].x, tValues); + } + if (!isBoundedByEndPoints(quad[0].y, quad[1].y, quad[2].y)) { + roots += findExtrema(quad[0].y, quad[1].y, quad[2].y, + &tValues[roots]); + } + for (int x = 0; x < roots; ++x) { + _Point result; + xy_at_t(quad, tValues[x], result.x, result.y); + add(result); + } +} + +void _Rect::setRawBounds(const Quadratic& quad) { + set(quad[0]); + for (int x = 1; x < 3; ++x) { + add(quad[x]); + } +} diff --git a/experimental/Intersection/QuadraticIntersection.cpp b/experimental/Intersection/QuadraticIntersection.cpp index f8c43b5e5d..a8e87ef6ea 100644 --- a/experimental/Intersection/QuadraticIntersection.cpp +++ b/experimental/Intersection/QuadraticIntersection.cpp @@ -97,7 +97,6 @@ bool intersect(double minT1, double maxT1, double minT2, double maxT2) { double newMinT2 = interp(minT2, maxT2, minT); double newMaxT2 = interp(minT2, maxT2, maxT); split = newMaxT2 - newMinT2 > (maxT2 - minT2) * tClipLimit; -#define VERBOSE 0 #if VERBOSE printf("%s d=%d s=%d new2=(%g,%g) old2=(%g,%g) split=%d\n", __FUNCTION__, depth, splits, newMinT2, newMaxT2, minT2, maxT2, split); @@ -144,6 +143,35 @@ int splits; }; bool intersect(const Quadratic& q1, const Quadratic& q2, Intersections& i) { + if (implicit_matches(q1, q2)) { + // FIXME: compute T values + // compute the intersections of the ends to find the coincident span + bool useVertical = fabs(q1[0].x - q1[2].x) < fabs(q1[0].y - q1[2].y); + double t; + if ((t = axialIntersect(q1, q2[0], useVertical)) >= 0) { + i.fT[0][0] = t; + i.fT[1][0] = 0; + i.fUsed++; + } + if ((t = axialIntersect(q1, q2[2], useVertical)) >= 0) { + i.fT[0][i.fUsed] = t; + i.fT[1][i.fUsed] = 1; + i.fUsed++; + } + useVertical = fabs(q2[0].x - q2[2].x) < fabs(q2[0].y - q2[2].y); + if ((t = axialIntersect(q2, q1[0], useVertical)) >= 0) { + i.fT[0][i.fUsed] = 0; + i.fT[1][i.fUsed] = t; + i.fUsed++; + } + if ((t = axialIntersect(q2, q1[2], useVertical)) >= 0) { + i.fT[0][i.fUsed] = 1; + i.fT[1][i.fUsed] = t; + i.fUsed++; + } + assert(i.fUsed <= 2); + return i.fUsed > 0; + } QuadraticIntersections q(q1, q2, i); return q.intersect(); } diff --git a/experimental/Intersection/QuadraticReduceOrder.cpp b/experimental/Intersection/QuadraticReduceOrder.cpp index e6ce04444a..4eef1a7781 100644 --- a/experimental/Intersection/QuadraticReduceOrder.cpp +++ b/experimental/Intersection/QuadraticReduceOrder.cpp @@ -21,7 +21,7 @@ static int vertical_line(const Quadratic& quad, Quadratic& reduction) { reduction[1] = quad[2]; int smaller = reduction[1].y > reduction[0].y; int larger = smaller ^ 1; - if (SkFindQuadExtrema(quad[0].y, quad[1].y, quad[2].y, &tValue)) { + if (findExtrema(quad[0].y, quad[1].y, quad[2].y, &tValue)) { double yExtrema = interp_quad_coords(quad[0].y, quad[1].y, quad[2].y, tValue); if (reduction[smaller].y > yExtrema) { reduction[smaller].y = yExtrema; @@ -38,7 +38,7 @@ static int horizontal_line(const Quadratic& quad, Quadratic& reduction) { reduction[1] = quad[2]; int smaller = reduction[1].x > reduction[0].x; int larger = smaller ^ 1; - if (SkFindQuadExtrema(quad[0].x, quad[1].x, quad[2].x, &tValue)) { + if (findExtrema(quad[0].x, quad[1].x, quad[2].x, &tValue)) { double xExtrema = interp_quad_coords(quad[0].x, quad[1].x, quad[2].x, tValue); if (reduction[smaller].x > xExtrema) { reduction[smaller].x = xExtrema; @@ -85,9 +85,9 @@ static int check_linear(const Quadratic& quad, Quadratic& reduction, double tValue; int root; if (useX) { - root = SkFindQuadExtrema(quad[0].x, quad[1].x, quad[2].x, &tValue); + root = findExtrema(quad[0].x, quad[1].x, quad[2].x, &tValue); } else { - root = SkFindQuadExtrema(quad[0].y, quad[1].y, quad[2].y, &tValue); + root = findExtrema(quad[0].y, quad[1].y, quad[2].y, &tValue); } if (root) { _Point extrema; @@ -146,8 +146,8 @@ int reduceOrder(const Quadratic& quad, Quadratic& reduction) { minYSet |= 1 << index; } } - if (minXSet == 0xF) { // test for vertical line - if (minYSet == 0xF) { // return 1 if all four are coincident + if (minXSet == 0x7) { // test for vertical line + if (minYSet == 0x7) { // return 1 if all four are coincident return coincident_line(quad, reduction); } return vertical_line(quad, reduction); diff --git a/experimental/Intersection/QuadraticUtilities.cpp b/experimental/Intersection/QuadraticUtilities.cpp index ea4cd851a7..4b4d79417e 100644 --- a/experimental/Intersection/QuadraticUtilities.cpp +++ b/experimental/Intersection/QuadraticUtilities.cpp @@ -25,5 +25,3 @@ int quadraticRoots(double A, double B, double C, double t[2]) { } return foundRoots; } - - diff --git a/experimental/Intersection/RectUtilities.cpp b/experimental/Intersection/RectUtilities.cpp index e7bda028ee..e69de29bb2 100644 --- a/experimental/Intersection/RectUtilities.cpp +++ b/experimental/Intersection/RectUtilities.cpp @@ -1,44 +0,0 @@ -#include "DataTypes.h" -#include "Extrema.h" - -void _Rect::setBounds(const Cubic& cubic) { - set(cubic[0]); - add(cubic[3]); - double tValues[4]; - int roots = SkFindCubicExtrema(cubic[0].x, cubic[1].x, cubic[2].x, - cubic[3].x, tValues); - roots += SkFindCubicExtrema(cubic[0].y, cubic[1].y, cubic[2].y, cubic[3].y, - &tValues[roots]); - for (int x = 0; x < roots; ++x) { - _Point result; - xy_at_t(cubic, tValues[x], result.x, result.y); - add(result); - } -} - -void _Rect::setRawBounds(const Cubic& cubic) { - set(cubic[0]); - for (int x = 1; x < 4; ++x) { - add(cubic[x]); - } -} - -void _Rect::setBounds(const Quadratic& quad) { - set(quad[0]); - add(quad[2]); - double tValues[2]; - int roots = SkFindQuadExtrema(quad[0].x, quad[1].x, quad[2].x, tValues); - roots += SkFindQuadExtrema(quad[0].y, quad[1].y, quad[2].y, &tValues[roots]); - for (int x = 0; x < roots; ++x) { - _Point result; - xy_at_t(quad, tValues[x], result.x, result.y); - add(result); - } -} - -void _Rect::setRawBounds(const Quadratic& quad) { - set(quad[0]); - for (int x = 1; x < 3; ++x) { - add(quad[x]); - } -} diff --git a/experimental/Intersection/Simplify.cpp b/experimental/Intersection/Simplify.cpp new file mode 100644 index 0000000000..5d7209a09d --- /dev/null +++ b/experimental/Intersection/Simplify.cpp @@ -0,0 +1,1213 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "CurveIntersection.h" +#include "Intersections.h" +#include "LineIntersection.h" +#include "SkPath.h" +#include "SkRect.h" +#include "SkTArray.h" +#include "SkTDArray.h" +#include "ShapeOps.h" +#include "TSearch.h" + +#undef SkASSERT +#define SkASSERT(cond) while (!(cond)) { sk_throw(); } + +// FIXME: remove once debugging is complete +#if 0 // set to 1 for no debugging whatsoever + +//const bool gxRunTestsInOneThread = false; + +#define DEBUG_ADD_INTERSECTING_TS 0 +#define DEBUG_BRIDGE 0 +#define DEBUG_DUMP 0 + +#else + +//const bool gRunTestsInOneThread = true; + +#define DEBUG_ADD_INTERSECTING_TS 1 +#define DEBUG_BRIDGE 1 +#define DEBUG_DUMP 1 + +#endif + +#if DEBUG_DUMP +static const char* kLVerbStr[] = {"", "line", "quad", "cubic"}; +static const char* kUVerbStr[] = {"", "Line", "Quad", "Cubic"}; +static int gContourID; +static int gSegmentID; +#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}}; + const _Line bLine = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}}; + return intersect(aLine, bLine, intersections.fT[0], intersections.fT[1]); +} + +static int QuadLineIntersect(const SkPoint a[3], const SkPoint b[2], + Intersections& intersections) { + const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}}; + const _Line bLine = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}}; + intersect(aQuad, bLine, intersections); + return intersections.fUsed; +} + +static int CubicLineIntersect(const SkPoint a[2], const SkPoint b[3], + 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}}; + const _Line bLine = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}}; + return intersect(aCubic, bLine, intersections.fT[0], intersections.fT[1]); +} + +static int QuadIntersect(const SkPoint a[3], const SkPoint b[3], + Intersections& intersections) { + const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}}; + const Quadratic bQuad = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}, {b[2].fX, b[2].fY}}; + intersect(aQuad, bQuad, intersections); + return intersections.fUsed; +} + +static int CubicIntersect(const SkPoint a[4], const SkPoint b[4], + 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}}; + const Cubic bCubic = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}, {b[2].fX, b[2].fY}, + {b[3].fX, b[3].fY}}; + intersect(aCubic, bCubic, intersections); + return intersections.fUsed; +} + +static int HLineIntersect(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 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}, + {a[3].fX, a[3].fY}}; + 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) { + 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); +} + +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; + xy_at_t(line, t, x, y); + out->fX = SkDoubleToScalar(x); + out->fY = SkDoubleToScalar(y); +} + +static void QuadXYAtT(const SkPoint a[3], double t, SkPoint* out) { + const Quadratic quad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}}; + double x, y; + xy_at_t(quad, t, x, y); + out->fX = SkDoubleToScalar(x); + out->fY = SkDoubleToScalar(y); +} + +static void CubicXYAtT(const SkPoint a[4], double t, SkPoint* out) { + 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, y; + xy_at_t(cubic, t, x, y); + out->fX = SkDoubleToScalar(x); + out->fY = SkDoubleToScalar(y); +} + +static void (* const SegmentXYAtT[])(const SkPoint [], double , SkPoint* ) = { + NULL, + LineXYAtT, + QuadXYAtT, + CubicXYAtT +}; + +static SkScalar LineXAtT(const SkPoint a[2], double t) { + const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}}; + double x; + xy_at_t(aLine, t, x, *(double*) 0); + return SkDoubleToScalar(x); +} + +static SkScalar QuadXAtT(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; + xy_at_t(quad, t, x, *(double*) 0); + return SkDoubleToScalar(x); +} + +static SkScalar CubicXAtT(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; + xy_at_t(cubic, t, x, *(double*) 0); + return SkDoubleToScalar(x); +} + +static SkScalar (* const SegmentXAtT[])(const SkPoint [], double ) = { + NULL, + LineXAtT, + QuadXAtT, + CubicXAtT +}; + +static SkScalar LineYAtT(const SkPoint a[2], double t) { + const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}}; + double y; + xy_at_t(aLine, t, *(double*) 0, y); + return SkDoubleToScalar(y); +} + +static SkScalar QuadYAtT(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 y; + xy_at_t(quad, t, *(double*) 0, y); + return SkDoubleToScalar(y); +} + +static SkScalar CubicYAtT(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 y; + xy_at_t(cubic, t, *(double*) 0, y); + return SkDoubleToScalar(y); +} + +static SkScalar (* const SegmentYAtT[])(const SkPoint [], double ) = { + NULL, + LineYAtT, + QuadYAtT, + CubicYAtT +}; + +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}}; + _Line dst; + sub_divide(aLine, startT, endT, dst); + sub[0].fX = SkDoubleToScalar(dst[0].x); + sub[0].fY = SkDoubleToScalar(dst[0].y); + sub[1].fX = SkDoubleToScalar(dst[1].x); + sub[1].fY = SkDoubleToScalar(dst[1].y); +} + +static void QuadSubDivide(const SkPoint a[3], double startT, double endT, + SkPoint sub[3]) { + const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, + {a[2].fX, a[2].fY}}; + Quadratic dst; + sub_divide(aQuad, startT, endT, dst); + sub[0].fX = SkDoubleToScalar(dst[0].x); + sub[0].fY = SkDoubleToScalar(dst[0].y); + sub[1].fX = SkDoubleToScalar(dst[1].x); + sub[1].fY = SkDoubleToScalar(dst[1].y); + sub[2].fX = SkDoubleToScalar(dst[2].x); + sub[2].fY = SkDoubleToScalar(dst[2].y); +} + +static void CubicSubDivide(const SkPoint a[4], double startT, double endT, + SkPoint sub[4]) { + 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}}; + Cubic dst; + sub_divide(aCubic, startT, endT, dst); + sub[0].fX = SkDoubleToScalar(dst[0].x); + sub[0].fY = SkDoubleToScalar(dst[0].y); + sub[1].fX = SkDoubleToScalar(dst[1].x); + sub[1].fY = SkDoubleToScalar(dst[1].y); + sub[2].fX = SkDoubleToScalar(dst[2].x); + sub[2].fY = SkDoubleToScalar(dst[2].y); + sub[3].fX = SkDoubleToScalar(dst[3].x); + sub[3].fY = SkDoubleToScalar(dst[3].y); +} + +static void QuadSubBounds(const SkPoint a[3], double startT, double endT, + SkRect& bounds) { + SkPoint dst[3]; + QuadSubDivide(a, startT, endT, dst); + bounds.fLeft = bounds.fRight = dst[0].fX; + bounds.fTop = bounds.fBottom = dst[0].fY; + for (int index = 1; index < 3; ++index) { + bounds.growToInclude(dst[index].fX, dst[index].fY); + } +} + +static void CubicSubBounds(const SkPoint a[4], double startT, double endT, + SkRect& bounds) { + SkPoint dst[4]; + CubicSubDivide(a, startT, endT, dst); + bounds.fLeft = bounds.fRight = dst[0].fX; + bounds.fTop = bounds.fBottom = dst[0].fY; + for (int index = 1; index < 4; ++index) { + bounds.growToInclude(dst[index].fX, dst[index].fY); + } +} + +static SkPath::Verb QuadReduceOrder(const SkPoint a[4], + SkTDArray<SkPoint>& reducePts) { + const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, + {a[2].fX, a[2].fY}}; + Quadratic dst; + int order = reduceOrder(aQuad, dst); + for (int index = 0; index < order; ++index) { + SkPoint* pt = reducePts.append(); + pt->fX = SkDoubleToScalar(dst[index].x); + pt->fY = SkDoubleToScalar(dst[index].y); + } + return (SkPath::Verb) (order - 1); +} + +static SkPath::Verb CubicReduceOrder(const SkPoint a[4], + SkTDArray<SkPoint>& reducePts) { + 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}}; + Cubic dst; + int order = reduceOrder(aCubic, dst, kReduceOrder_QuadraticsAllowed); + for (int index = 0; index < order; ++index) { + SkPoint* pt = reducePts.append(); + pt->fX = SkDoubleToScalar(dst[index].x); + pt->fY = SkDoubleToScalar(dst[index].y); + } + return (SkPath::Verb) (order - 1); +} + +static SkScalar LineLeftMost(const SkPoint a[2], double startT, double endT) { + const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}}; + double x[2]; + xy_at_t(aLine, startT, x[0], *(double*) 0); + xy_at_t(aLine, endT, x[0], *(double*) 0); + return startT < endT ? startT : endT; +} + +static SkScalar QuadLeftMost(const SkPoint a[3], double startT, double endT) { + const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, + {a[2].fX, a[2].fY}}; + return leftMostT(aQuad, startT, endT); +} + +static SkScalar CubicLeftMost(const SkPoint a[4], double startT, double endT) { + 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 leftMostT(aCubic, startT, endT); +} + +static SkScalar (* const SegmentLeftMost[])(const SkPoint [], double , double) = { + NULL, + LineLeftMost, + QuadLeftMost, + CubicLeftMost +}; + +static bool IsCoincident(const SkPoint a[2], const SkPoint& above, + const SkPoint& below) { + const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}}; + const _Line bLine = {{above.fX, above.fY}, {below.fX, below.fY}}; + return implicit_matches_ulps(aLine, bLine, 32); +} + +// Bounds, unlike Rect, does not consider a vertical 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; + } + + bool isEmpty() { + return fLeft > fRight || fTop > fBottom + || fLeft == fRight && fTop == fBottom + || isnan(fLeft) || isnan(fRight) + || isnan(fTop) || isnan(fBottom); + } + + void setCubicBounds(const SkPoint a[4]) { + _Rect dRect; + 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}}; + dRect.setBounds(cubic); + set(dRect.left, dRect.top, dRect.right, dRect.bottom); + } + + void setQuadBounds(const SkPoint a[3]) { + const Quadratic quad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, + {a[2].fX, a[2].fY}}; + _Rect dRect; + dRect.setBounds(quad); + set(dRect.left, dRect.top, dRect.right, dRect.bottom); + } +}; + +class Segment; + +struct TEntry { + double fT; + const Segment* fOther; + double fOtherT; + bool fCoincident; +}; + +class Segment { +public: + Segment() { +#if DEBUG_DUMP + fID = ++gSegmentID; +#endif + } + + int addT(double newT, const Segment& other) { + // FIXME: in the pathological case where there is a ton of intercepts, + // binary search? + int insertedAt = -1; + TEntry* entry; + size_t tCount = fTs.count(); + double delta; + for (size_t idx2 = 0; idx2 < tCount; ++idx2) { + if (newT <= fTs[idx2].fT) { + insertedAt = idx2; + entry = fTs.insert(idx2); + goto finish; + } + } + insertedAt = tCount; + entry = fTs.append(); +finish: + entry->fT = newT; + entry->fOther = &other; + return insertedAt; + } + + bool addCubic(const SkPoint pts[4]) { + fPts = pts; + fVerb = SkPath::kCubic_Verb; + fBounds.setCubicBounds(pts); + } + + bool addLine(const SkPoint pts[2]) { + fPts = pts; + fVerb = SkPath::kLine_Verb; + fBounds.set(pts, 2); + } + + // add 2 to edge or out of range values to get T extremes + void addOtherT(int index, double other, bool coincident) { + fTs[index].fOtherT = other; + fTs[index].fCoincident = coincident; + } + + bool addQuad(const SkPoint pts[3]) { + fPts = pts; + fVerb = SkPath::kQuad_Verb; + fBounds.setQuadBounds(pts); + } + + const Bounds& bounds() const { + return fBounds; + } + + int findByT(double t, const Segment* match) const { + // OPTIMIZATION: bsearch if count is honkin huge + int count = fTs.count(); + for (int index = 0; index < count; ++index) { + const TEntry& entry = fTs[index]; + if (t == entry.fT && match == entry.fOther) { + return index; + } + } + SkASSERT(0); // should never get here + return -1; + } + + int findLefty(int tIndex, const SkPoint& base) const { + int bestTIndex; + SkPoint test; + SkScalar bestX = DBL_MAX; + int testTIndex = tIndex; + while (--testTIndex >= 0) { + xyAtT(testTIndex, &test); + if (test != base) { + continue; + } + bestX = test.fX; + bestTIndex = testTIndex; + break; + } + int count = fTs.count(); + testTIndex = tIndex; + while (++testTIndex < count) { + xyAtT(testTIndex, &test); + if (test == base) { + continue; + } + return bestX > test.fX ? testTIndex : bestTIndex; + } + return -1; + } + + const Segment* findTop(int& tIndex) const { + // iterate through T intersections and return topmost + // topmost tangent from y-min to first pt is closer to horizontal + int firstT = 0; + int lastT = 0; + SkScalar topY = fPts[0].fY; + int count = fTs.count(); + int index; + for (index = 1; index < count; ++index) { + const TEntry& entry = fTs[index]; + double t = entry.fT; + SkScalar yIntercept = (*SegmentYAtT[fVerb])(fPts, t); + if (topY > yIntercept) { + topY = yIntercept; + firstT = lastT = index; + } else if (topY == yIntercept) { + lastT = index; + } + } + // if a pair of segments go down, choose the higher endpoint + if (firstT == lastT && (firstT == 0 || firstT == count - 1)) { + tIndex = firstT; + return this; + } + // if the topmost T is not on end, or is three-way or more, find left + SkPoint leftBase; + xyAtT(firstT, &leftBase); + int tLeft = findLefty(firstT, leftBase); + SkASSERT(tLeft > 0); + const Segment* leftSegment = this; + SkScalar left = leftMost(firstT, tLeft); + for (index = firstT; index <= lastT; ++index) { + const Segment* other = fTs[index].fOther; + double otherT = fTs[index].fOtherT; + int otherTIndex = other->findByT(otherT, this); + // pick companionT closest (but not too close) on either side + int otherTLeft = other->findLefty(otherTIndex, leftBase); + if (otherTLeft < 0) { + continue; + } + SkScalar otherMost = other->leftMost(otherTIndex, otherTLeft); + if (otherMost < left) { + leftSegment = other; + } + } + return leftSegment; + } + + bool intersected() const { + return fTs.count() > 0; + } + + bool isHorizontal() const { + return fBounds.fTop == fBounds.fBottom; + } + + bool isVertical() const { + return fBounds.fLeft == fBounds.fRight; + } + + SkScalar leftMost(int start, int end) const { + return (*SegmentLeftMost[fVerb])(fPts, fTs[start].fT, fTs[end].fT); + } + + const SkPoint* pts() const { + return fPts; + } + + void reset() { + fPts = NULL; + fVerb = (SkPath::Verb) -1; + fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax); + fTs.reset(); + } + + // OPTIMIZATION: remove this function if it's never called + double t(int tIndex) const { + return fTs[tIndex].fT; + } + + SkPath::Verb verb() const { + return fVerb; + } + + SkScalar xAtT(double t) const { + return (*SegmentXAtT[fVerb])(fPts, t); + } + + void xyAtT(double t, SkPoint* pt) const { + (*SegmentXYAtT[fVerb])(fPts, t, pt); + } + +#if DEBUG_DUMP + void dump() const { + const char className[] = "Segment"; + const int tab = 4; + for (int i = 0; i < fTs.count(); ++i) { + 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 coincident=%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].fCoincident); + } + SkDebugf("%*s [%d] fBounds=(l:%1.9g, t:%1.9g r:%1.9g, b:%1.9g)\n", + tab + sizeof(className), className, fID, + fBounds.fLeft, fBounds.fTop, fBounds.fRight, fBounds.fBottom); + } +#endif + +private: + const SkPoint* fPts; + SkPath::Verb fVerb; + Bounds fBounds; + SkTDArray<TEntry> fTs; +#if DEBUG_DUMP + int fID; +#endif +}; + +class Contour { +public: + Contour() { + reset(); +#if DEBUG_DUMP + fID = ++gContourID; +#endif + } + + bool operator<(const Contour& rh) const { + return fBounds.fTop == rh.fBounds.fTop + ? fBounds.fLeft < rh.fBounds.fLeft + : fBounds.fTop < rh.fBounds.fTop; + } + + void addCubic(const SkPoint pts[4]) { + fSegments.push_back().addCubic(pts); + fContainsCurves = true; + } + void addLine(const SkPoint pts[2]) { + fSegments.push_back().addLine(pts); + } + + void addQuad(const SkPoint pts[3]) { + fSegments.push_back().addQuad(pts); + fContainsCurves = true; + } + + const Bounds& bounds() const { + return fBounds; + } + + void complete() { + setBounds(); + fContainsIntercepts = false; + } + + void containsIntercepts() { + fContainsIntercepts = true; + } + + void reset() { + fSegments.reset(); + fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax); + fContainsCurves = fContainsIntercepts = false; + } + + Segment& topSegment() { + return fSegments[fTopSegment]; + } + +#if DEBUG_DUMP + void dump() { + int i; + const char className[] = "Contour"; + const int tab = 4; + SkDebugf("%s %p (contour=%d)\n", className, this, fID); + for (i = 0; i < fSegments.count(); ++i) { + SkDebugf("%*s.fSegments[%d]:\n", tab + sizeof(className), + className, i); + fSegments[i].dump(); + } + SkDebugf("%*s.fBounds=(l:%1.9g, t:%1.9g r:%1.9g, b:%1.9g)\n", + tab + sizeof(className), className, + fBounds.fLeft, fBounds.fTop, + fBounds.fRight, fBounds.fBottom); + SkDebugf("%*s.fTopSegment=%d\n", tab + sizeof(className), className, + fTopSegment); + SkDebugf("%*s.fContainsIntercepts=%d\n", tab + sizeof(className), + className, fContainsIntercepts); + SkDebugf("%*s.fContainsCurves=%d\n", tab + sizeof(className), + className, fContainsCurves); + } +#endif + +protected: + void setBounds() { + int count = fSegments.count(); + if (count == 0) { + SkDebugf("%s empty contour\n", __FUNCTION__); + SkASSERT(0); + // FIXME: delete empty contour? + return; + } + fTopSegment = 0; + fBounds = fSegments.front().bounds(); + SkScalar top = fBounds.fTop; + for (int index = 1; index < count; ++index) { + fBounds.growToInclude(fSegments[index].bounds()); + if (top > fBounds.fTop) { + fTopSegment = index; + top = fBounds.fTop; + } + } + } + +public: + SkTArray<Segment> fSegments; // not worth accessor functions? + +private: + Bounds fBounds; + int fTopSegment; + bool fContainsIntercepts; + bool fContainsCurves; +#if DEBUG_DUMP + int fID; +#endif +}; + +class EdgeBuilder { +public: + +EdgeBuilder(const SkPath& path, SkTArray<Contour>& contours) + : fPath(path) + , fCurrentContour(NULL) + , fContours(contours) +{ +#if DEBUG_DUMP + gContourID = 0; + gSegmentID = 0; +#endif + walk(); +} + +protected: + +void complete() { + if (fCurrentContour && fCurrentContour->fSegments.count()) { + fCurrentContour->complete(); + fCurrentContour = NULL; + } +} + +void startContour() { + if (!fCurrentContour) { + fCurrentContour = fContours.push_back_n(1); + } +} + +void walk() { + // FIXME:remove once we can access path pts directly + SkPath::RawIter iter(fPath); // FIXME: access path directly when allowed + SkPoint pts[4]; + SkPath::Verb verb; + do { + verb = iter.next(pts); + *fPathVerbs.append() = verb; + if (verb == SkPath::kMove_Verb) { + *fPathPts.append() = pts[0]; + } else if (verb >= SkPath::kLine_Verb && verb <= SkPath::kCubic_Verb) { + fPathPts.append(verb, &pts[1]); + } + } while (verb != SkPath::kDone_Verb); + // FIXME: end of section to remove once path pts are accessed directly + + SkPath::Verb reducedVerb; + uint8_t* verbPtr = fPathVerbs.begin(); + const SkPoint* pointsPtr = fPathPts.begin(); + while ((verb = (SkPath::Verb) *verbPtr++) != SkPath::kDone_Verb) { + switch (verb) { + case SkPath::kMove_Verb: + complete(); + startContour(); + pointsPtr += 1; + continue; + case SkPath::kLine_Verb: + // skip degenerate points + if (pointsPtr[-1].fX != pointsPtr[0].fX + || pointsPtr[-1].fY != pointsPtr[0].fY) { + fCurrentContour->addLine(&pointsPtr[-1]); + } + break; + case SkPath::kQuad_Verb: + reducedVerb = QuadReduceOrder(&pointsPtr[-1], fReducePts); + if (reducedVerb == 0) { + break; // skip degenerate points + } + if (reducedVerb == 1) { + fCurrentContour->addLine(fReducePts.end() - 2); + break; + } + fCurrentContour->addQuad(&pointsPtr[-1]); + break; + case SkPath::kCubic_Verb: + reducedVerb = CubicReduceOrder(&pointsPtr[-1], fReducePts); + if (reducedVerb == 0) { + break; // skip degenerate points + } + if (reducedVerb == 1) { + fCurrentContour->addLine(fReducePts.end() - 2); + break; + } + if (reducedVerb == 2) { + fCurrentContour->addQuad(fReducePts.end() - 3); + break; + } + fCurrentContour->addCubic(&pointsPtr[-1]); + break; + case SkPath::kClose_Verb: + SkASSERT(fCurrentContour); + complete(); + continue; + default: + SkDEBUGFAIL("bad verb"); + return; + } + pointsPtr += verb; + SkASSERT(fCurrentContour); + } + complete(); + if (fCurrentContour && !fCurrentContour->fSegments.count()) { + fContours.pop_back(); + } +} + +private: + const SkPath& fPath; + SkTDArray<SkPoint> fPathPts; // FIXME: point directly to path pts instead + SkTDArray<uint8_t> fPathVerbs; // FIXME: remove + Contour* fCurrentContour; + SkTArray<Contour>& fContours; + SkTDArray<SkPoint> fReducePts; // segments created on the fly +}; + +class Work { +public: + enum SegmentType { + kHorizontalLine_Segment = -1, + kVerticalLine_Segment = 0, + kLine_Segment = SkPath::kLine_Verb, + kQuad_Segment = SkPath::kQuad_Verb, + kCubic_Segment = SkPath::kCubic_Verb, + }; + + void addOtherT(int index, double other, bool coincident) { + fContour->fSegments[fIndex].addOtherT(index, other, coincident); + } + + // Avoid collapsing t values that are close to the same since + // we walk ts to describe consecutive intersections. Since a pair of ts can + // be nearly equal, any problems caused by this should be taken care + // of later. + // On the edge or out of range values are negative; add 2 to get end + int addT(double newT, const Work& other) { + fContour->containsIntercepts(); + return fContour->fSegments[fIndex].addT(newT, + other.fContour->fSegments[other.fIndex]); + } + + bool advance() { + return ++fIndex < fLast; + } + + SkScalar bottom() const { + return bounds().fBottom; + } + + const Bounds& bounds() const { + return fContour->fSegments[fIndex].bounds(); + } + + const SkPoint* cubic() const { + return fCubic; + } + + void init(Contour* contour) { + fContour = contour; + fIndex = 0; + fLast = contour->fSegments.count(); + } + + SkScalar left() const { + return bounds().fLeft; + } + + void promoteToCubic() { + fCubic[0] = pts()[0]; + fCubic[2] = pts()[1]; + fCubic[3] = pts()[2]; + fCubic[1].fX = (fCubic[0].fX + fCubic[2].fX * 2) / 3; + fCubic[1].fY = (fCubic[0].fY + fCubic[2].fY * 2) / 3; + fCubic[2].fX = (fCubic[3].fX + fCubic[2].fX * 2) / 3; + fCubic[2].fY = (fCubic[3].fY + fCubic[2].fY * 2) / 3; + } + + const SkPoint* pts() const { + return fContour->fSegments[fIndex].pts(); + } + + SkScalar right() const { + return bounds().fRight; + } + + ptrdiff_t segmentIndex() const { + return fIndex; + } + + SegmentType segmentType() const { + const Segment& segment = fContour->fSegments[fIndex]; + SegmentType type = (SegmentType) segment.verb(); + if (type != kLine_Segment) { + return type; + } + if (segment.isHorizontal()) { + return kHorizontalLine_Segment; + } + if (segment.isVertical()) { + return kVerticalLine_Segment; + } + return kLine_Segment; + } + + bool startAfter(const Work& after) { + fIndex = after.fIndex; + return advance(); + } + + SkScalar top() const { + return bounds().fTop; + } + + SkPath::Verb verb() const { + return fContour->fSegments[fIndex].verb(); + } + + SkScalar x() const { + return bounds().fLeft; + } + + bool xFlipped() const { + return x() != pts()[0].fX; + } + + SkScalar y() const { + return bounds().fTop; + } + + bool yFlipped() const { + return y() != pts()[0].fX; + } + +protected: + Contour* fContour; + SkPoint fCubic[4]; + int fIndex; + int fLast; +}; + +static void debugShowLineIntersection(int pts, const Work& wt, + const Work& wn, const double wtTs[2], const double wnTs[2]) { +#if DEBUG_ADD_INTERSECTING_TS + if (!pts) { + return; + } + SkPoint wtOutPt, wnOutPt; + LineXYAtT(wt.pts(), wtTs[0], &wtOutPt); + LineXYAtT(wn.pts(), wnTs[0], &wnOutPt); + SkDebugf("%s wtTs[0]=%g (%g,%g, %g,%g) (%g,%g)\n", + __FUNCTION__, + wtTs[0], wt.pts()[0].fX, wt.pts()[0].fY, + wt.pts()[1].fX, wt.pts()[1].fY, wtOutPt.fX, wtOutPt.fY); + if (pts == 2) { + SkDebugf("%s wtTs[1]=%g\n", __FUNCTION__, wtTs[1]); + } + SkDebugf("%s wnTs[0]=%g (%g,%g, %g,%g) (%g,%g)\n", + __FUNCTION__, + wnTs[0], wn.pts()[0].fX, wn.pts()[0].fY, + wn.pts()[1].fX, wn.pts()[1].fY, wnOutPt.fX, wnOutPt.fY); + if (pts == 2) { + SkDebugf("%s wnTs[1]=%g\n", __FUNCTION__, wnTs[1]); + } +#endif +} + +static bool addIntersectingTs(Contour* test, Contour* next) { + if (test != next) { + if (test->bounds().fBottom < next->bounds().fTop) { + return false; + } + if (!Bounds::Intersects(test->bounds(), next->bounds())) { + return true; + } + } + Work wt, wn; + wt.init(test); + wn.init(next); + do { + if (test == next && !wn.startAfter(wt)) { + continue; + } + do { + if (!Bounds::Intersects(wt.bounds(), wn.bounds())) { + continue; + } + int pts; + Intersections ts; + bool swap = false; + switch (wt.segmentType()) { + case Work::kHorizontalLine_Segment: + swap = true; + switch (wn.segmentType()) { + case Work::kHorizontalLine_Segment: + case Work::kVerticalLine_Segment: + case Work::kLine_Segment: { + pts = HLineIntersect(wn.pts(), wt.left(), + wt.right(), wt.y(), wt.xFlipped(), ts); + break; + } + case Work::kQuad_Segment: { + pts = HQuadIntersect(wn.pts(), wt.left(), + wt.right(), wt.y(), wt.xFlipped(), ts); + break; + } + case Work::kCubic_Segment: { + pts = HCubicIntersect(wn.pts(), wt.left(), + wt.right(), wt.y(), wt.xFlipped(), ts); + break; + } + default: + SkASSERT(0); + } + break; + case Work::kVerticalLine_Segment: + swap = true; + switch (wn.segmentType()) { + case Work::kHorizontalLine_Segment: + case Work::kVerticalLine_Segment: + case Work::kLine_Segment: { + pts = VLineIntersect(wn.pts(), wt.top(), + wt.bottom(), wt.x(), wt.yFlipped(), ts); + break; + } + case Work::kQuad_Segment: { + pts = VQuadIntersect(wn.pts(), wt.top(), + wt.bottom(), wt.x(), wt.yFlipped(), ts); + break; + } + case Work::kCubic_Segment: { + pts = VCubicIntersect(wn.pts(), wt.top(), + wt.bottom(), wt.x(), wt.yFlipped(), ts); + break; + } + default: + SkASSERT(0); + } + break; + case Work::kLine_Segment: + switch (wn.segmentType()) { + case Work::kHorizontalLine_Segment: + pts = HLineIntersect(wt.pts(), wn.left(), + wn.right(), wn.y(), wn.xFlipped(), ts); + break; + case Work::kVerticalLine_Segment: + pts = VLineIntersect(wt.pts(), wn.top(), + wn.bottom(), wn.x(), wn.yFlipped(), ts); + break; + case Work::kLine_Segment: { + pts = LineIntersect(wt.pts(), wn.pts(), ts); + debugShowLineIntersection(pts, wt, wn, + ts.fT[1], ts.fT[0]); + break; + } + case Work::kQuad_Segment: { + swap = true; + pts = QuadLineIntersect(wn.pts(), wt.pts(), ts); + break; + } + case Work::kCubic_Segment: { + swap = true; + pts = CubicLineIntersect(wn.pts(), wt.pts(), ts); + break; + } + default: + SkASSERT(0); + } + break; + case Work::kQuad_Segment: + switch (wn.segmentType()) { + case Work::kHorizontalLine_Segment: + pts = HQuadIntersect(wt.pts(), wn.left(), + wn.right(), wn.y(), wn.xFlipped(), ts); + break; + case Work::kVerticalLine_Segment: + pts = VQuadIntersect(wt.pts(), wn.top(), + wn.bottom(), wn.x(), wn.yFlipped(), ts); + break; + case Work::kLine_Segment: { + pts = QuadLineIntersect(wt.pts(), wn.pts(), ts); + break; + } + case Work::kQuad_Segment: { + pts = QuadIntersect(wt.pts(), wn.pts(), ts); + break; + } + case Work::kCubic_Segment: { + wt.promoteToCubic(); + pts = CubicIntersect(wt.cubic(), wn.pts(), ts); + break; + } + default: + SkASSERT(0); + } + break; + case Work::kCubic_Segment: + switch (wn.segmentType()) { + case Work::kHorizontalLine_Segment: + pts = HCubicIntersect(wt.pts(), wn.left(), + wn.right(), wn.y(), wn.xFlipped(), ts); + break; + case Work::kVerticalLine_Segment: + pts = VCubicIntersect(wt.pts(), wn.top(), + wn.bottom(), wn.x(), wn.yFlipped(), ts); + break; + case Work::kLine_Segment: { + pts = CubicLineIntersect(wt.pts(), wn.pts(), ts); + break; + } + case Work::kQuad_Segment: { + wn.promoteToCubic(); + pts = CubicIntersect(wt.pts(), wn.cubic(), ts); + break; + } + case Work::kCubic_Segment: { + pts = CubicIntersect(wt.pts(), wn.pts(), ts); + break; + } + default: + SkASSERT(0); + } + break; + default: + SkASSERT(0); + } + // in addition to recording T values, record matching segment + bool coincident = pts == 2 && wn.segmentType() <= Work::kLine_Segment + && wt.segmentType() <= Work::kLine_Segment; + 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); + int nextTAt = wn.addT(ts.fT[!swap][pt], wt); + wt.addOtherT(testTAt, ts.fT[!swap][pt], coincident); + wn.addOtherT(nextTAt, ts.fT[swap][pt], coincident); + } + } while (wn.advance()); + } while (wt.advance()); + return true; +} + +// Each segment may have an inside or an outside. Segments contained within +// winding may have insides on either side, and form a contour that should be +// ignored. Segments that are coincident with opposing direction segments may +// have outsides on either side, and should also disappear. +// 'Normal' segments will have one inside and one outside. Subsequent connections +// when winding should follow the intersection direction. If more than one edge +// is an option, choose first edge that continues the inside. + +static void bridge(SkTDArray<Contour*>& contourList) { + // Start at the top. Above the top is outside, below is inside. + Contour* topContour = contourList[0]; + Segment& topStart = topContour->topSegment(); + int index; + const Segment* topSegment = topStart.findTop(index); + start here ; + // find span + // handle coincident + // mark neighbors winding coverage + // output span + // mark span as processed + +} + +static void makeContourList(SkTArray<Contour>& contours, Contour& sentinel, + SkTDArray<Contour*>& list) { + size_t count = contours.count(); + if (count == 0) { + return; + } + for (size_t index = 0; index < count; ++index) { + *list.append() = &contours[index]; + } + *list.append() = &sentinel; + QSort<Contour>(list.begin(), list.end() - 1); +} + +void simplifyx(const SkPath& path, bool asFill, SkPath& simple) { + // returns 1 for evenodd, -1 for winding, regardless of inverse-ness + int windingMask = (path.getFillType() & 1) ? 1 : -1; + simple.reset(); + simple.setFillType(SkPath::kEvenOdd_FillType); + + // turn path into list of segments + SkTArray<Contour> contours; + // FIXME: add self-intersecting cubics' T values to segment + EdgeBuilder builder(path, contours); + SkTDArray<Contour*> contourList; + Contour sentinel; + sentinel.reset(); + makeContourList(contours, sentinel, contourList); + Contour** currentPtr = contourList.begin(); + if (!currentPtr) { + return; + } + // find all intersections between segments + do { + Contour** nextPtr = currentPtr; + Contour* current = *currentPtr++; + Contour* next; + do { + next = *nextPtr++; + } while (next != &sentinel && addIntersectingTs(current, next)); + } while (*currentPtr != &sentinel); + // construct closed contours + bridge(contourList); +} + diff --git a/experimental/Intersection/SimplifyAddIntersectingTs_Test.cpp b/experimental/Intersection/SimplifyAddIntersectingTs_Test.cpp new file mode 100644 index 0000000000..fff07da646 --- /dev/null +++ b/experimental/Intersection/SimplifyAddIntersectingTs_Test.cpp @@ -0,0 +1,132 @@ +#include "CurveIntersection.h" +#include "Intersections.h" +#include "LineIntersection.h" +#include "SkPath.h" +#include "SkRect.h" +#include "SkTArray.h" +#include "SkTDArray.h" +#include "ShapeOps.h" +#include "TSearch.h" + +namespace SimplifyAddIntersectingTsTest { + +#include "Simplify.cpp" + +} // end of SimplifyAddIntersectingTsTest namespace + +#include "Intersection_Tests.h" + +static const SkPoint lines[][2] = { + {{ 1, 1}, { 1, 1}}, // degenerate + {{ 1, 1}, { 4, 1}}, // horizontal + {{ 4, 1}, { 9, 1}}, + {{ 2, 1}, { 3, 1}}, + {{ 2, 1}, { 6, 1}}, + {{ 5, 1}, { 9, 1}}, + {{ 1, 1}, { 1, 4}}, // vertical + {{ 1, 2}, { 1, 3}}, + {{ 1, 2}, { 1, 6}}, + {{ 1, 5}, { 1, 9}}, + {{ 1, 1}, { 3, 3}}, // diagonal + {{ 2, 2}, { 4, 4}}, + {{ 2, 4}, { 4, 2}}, +}; + +static const size_t lineCount = sizeof(lines) / sizeof(lines[0]); + +static const SkPoint quads[][3] = { + {{ 1, 1}, { 1, 1}, { 1, 1}}, // degenerate + {{ 1, 1}, { 4, 1}, { 5, 1}}, // line + {{ 1, 1}, { 4, 1}, { 4, 4}}, // curve +}; + +static const size_t quadCount = sizeof(quads) / sizeof(quads[0]); + +static const SkPoint cubics[][4] = { + {{ 1, 1}, { 1, 1}, { 1, 1}, { 1, 1}}, // degenerate + {{ 1, 1}, { 4, 1}, { 5, 1}, { 6, 1}}, // line + {{ 1, 1}, { 3, 1}, { 4, 2}, { 4, 4}}, // curve +}; + +static const size_t cubicCount = sizeof(cubics) / sizeof(cubics[0]); +static const size_t testCount = lineCount + quadCount + cubicCount; + +static SkPath::Verb setPath(int outer, SkPath& path, const SkPoint*& pts1) { + SkPath::Verb c1Type; + if (outer < lineCount) { + path.moveTo(lines[outer][0].fX, lines[outer][0].fY); + path.lineTo(lines[outer][1].fX, lines[outer][1].fY); + c1Type = SkPath::kLine_Verb; + pts1 = lines[outer]; + } else { + outer -= lineCount; + if (outer < quadCount) { + path.moveTo(quads[outer][0].fX, quads[outer][0].fY); + path.quadTo(quads[outer][1].fX, quads[outer][1].fY, + quads[outer][2].fX, quads[outer][2].fY); + c1Type = SkPath::kQuad_Verb; + pts1 = quads[outer]; + } else { + outer -= quadCount; + path.moveTo(cubics[outer][0].fX, cubics[outer][0].fY); + path.cubicTo(cubics[outer][1].fX, cubics[outer][1].fY, + cubics[outer][2].fX, cubics[outer][2].fY, + cubics[outer][3].fX, cubics[outer][3].fY); + c1Type = SkPath::kCubic_Verb; + pts1 = cubics[outer]; + } + } + return c1Type; +} + +static void testPath(const SkPath& path, const SkPoint* pts1, SkPath::Verb c1Type, + const SkPoint* pts2, SkPath::Verb c2Type) { + SkTArray<SimplifyAddIntersectingTsTest::Contour> contour; + SimplifyAddIntersectingTsTest::EdgeBuilder builder(path, contour); + if (contour.count() < 2) { + return; + } + SimplifyAddIntersectingTsTest::Contour& c1 = contour[0]; + SimplifyAddIntersectingTsTest::Contour& c2 = contour[1]; + addIntersectingTs(&c1, &c2); + bool c1Intersected = c1.fSegments[0].intersected(); + bool c2Intersected = c2.fSegments[0].intersected(); +#if DEBUG_DUMP + SkDebugf("%s %s (%1.9g,%1.9g %1.9g,%1.9g) %s %s (%1.9g,%1.9g %1.9g,%1.9g)\n", + __FUNCTION__, SimplifyAddIntersectingTsTest::kLVerbStr[c1Type], + pts1[0].fX, pts1[0].fY, + pts1[c1Type].fX, pts1[c1Type].fY, + c1Intersected ? "intersects" : "does not intersect", + SimplifyAddIntersectingTsTest::kLVerbStr[c2Type], + pts2[0].fX, pts2[0].fY, + pts2[c2Type].fX, pts2[c2Type].fY); + if (c1Intersected) { + c1.dump(); + c2.dump(); + } +#endif +} + +static const int firstO = 6; +static const int firstI = 1; + +void SimplifyAddIntersectingTs_Test() { + const SkPoint* pts1, * pts2; + if (firstO > 0 || firstI > 0) { + SkPath path; + SkPath::Verb c1Type = setPath(firstO, path, pts1); + SkPath path2(path); + SkPath::Verb c2Type = setPath(firstI, path2, pts2); + testPath(path2, pts1, c1Type, pts2, c2Type); + } + for (int o = 0; o < testCount; ++o) { + SkPath path; + SkPath::Verb c1Type = setPath(o, path, pts1); + for (int i = 0; i < testCount; ++i) { + SkPath path2(path); + SkPath::Verb c2Type = setPath(i, path2, pts2); + testPath(path2, pts1, c1Type, pts2, c2Type); + } + } +} + diff --git a/experimental/Intersection/op.htm b/experimental/Intersection/op.htm index 4f12c91e79..4df4aeb1c1 100644 --- a/experimental/Intersection/op.htm +++ b/experimental/Intersection/op.htm @@ -1,184 +1,178 @@ <html> <head> <div style="height:0"> -<div id="test_1div"> -path.moveTo(213.673737, 413.292938); -path.lineTo(225.200134, 343.616821); -path.lineTo(236.726532, 273.940704); -path.lineTo(219.386414, 231.373322); -path.lineTo(213.673737, 413.292938); -path.close(); -path.moveTo(43.485352, 308.984497); -path.lineTo(122.610657, 305.950134); -path.lineTo(201.735962, 302.915802); -path.lineTo(280.861267, 299.881470); -path.lineTo(43.485352, 308.984497); -path.close(); -</div> -<div id="test_2div"> -path.moveTo(-177.878387, 265.368988); -path.lineTo(-254.415771, 303.709961); -path.lineTo(-317.465363, 271.325562); -path.lineTo(-374.520386, 207.507660); -path.lineTo(-177.878387, 265.368988); -path.close(); -path.moveTo(-63.582489, -3.679123); -path.lineTo(-134.496841, 26.434566); -path.lineTo(-205.411209, 56.548256); -path.lineTo(-276.325562, 86.661942); -path.lineTo(-63.582489, -3.679123); -path.close(); -path.moveTo(-57.078423, 162.633453); -path.lineTo(-95.963928, 106.261139); -path.lineTo(-134.849457, 49.888824); -path.lineTo(-173.734955, -6.483480); -path.lineTo(-57.078423, 162.633453); -path.close(); + +<div id="testSimplifyQuadratic1"> + SkPath path, out; + path.moveTo(0, 0); + path.quadTo(1, 0, 1, 1); + path.close(); + path.moveTo(1, 0); + path.quadTo(0, 0, 0, 1); + path.close(); + testSimplify(path, true, out, bitmap); +} </div> -<div id="test_3div"> -path.moveTo(98.666489, -94.295059); -path.lineTo(156.584320, -61.939133); -path.lineTo(174.672974, -12.343765); -path.lineTo(158.622345, 52.028267); -path.lineTo(98.666489, -94.295059); -path.close(); -path.moveTo(-133.225616, -48.622055); -path.lineTo(-73.855499, -10.375397); -path.lineTo(-14.485367, 27.871277); -path.lineTo(44.884750, 66.117935); -path.lineTo(-133.225616, -48.622055); -path.close(); -path.moveTo( 9.030045, -163.413132); -path.lineTo(-19.605331, -89.588760); -path.lineTo(-48.240707, -15.764404); -path.lineTo(-76.876053, 58.059944); -path.lineTo( 9.030045, -163.413132); -path.close(); + +<div id="testSimplifyQuadratic2"> + SkPath path, out; + path.moveTo(0, 0); + path.quadTo(20, 0, 20, 20); + path.close(); + path.moveTo(20, 0); + path.quadTo(0, 0, 0, 20); + path.close(); + testSimplify(path, true, out, bitmap); +} </div> -<div id="test_4div"> -path.moveTo(340.41568, -170.97171); -path.lineTo(418.846893, -142.428329); -path.lineTo(497.278107, -113.884933); -path.lineTo(449.18222, -45.6723022); -path.lineTo(340.41568, -170.97171); -path.close(); -path.moveTo(326.610535, 34.0393639); -path.lineTo(371.334595, -14.9620667); -path.lineTo(416.058624, -63.9634857); -path.lineTo(460.782654, -112.96492); -path.lineTo(326.610535, 34.0393639); -path.close(); + +<div id="testSimplifyQuadratic3"> + SkPath path, out; + path.moveTo(0, 0); + path.quadTo(20, 0, 20, 20); + path.close(); + path.moveTo(0, 20); + path.quadTo(0, 0, 20, 0); + path.close(); + testSimplify(path, true, out, bitmap); +} </div> -<div id="test_5div"> -original: -path.moveTo(0, 0); -path.quadTo(20, 0, 20, 20); -path.lineTo(0, 0); -path.close(); -path.moveTo(0, 20); -path.quadTo(0, 0, 20, 0); -path.lineTo(0, 20); -path.close(); -simplified: -path.moveTo(0, 0); -path.lineTo(5, 5); -path.quadTo(0, 10, 0, 20); -path.lineTo(20, 20); -path.quadTo(20, 10, 15, 5); -path.lineTo(20, 0); -path.quadTo(14.1421356, 0, 10, 1.71572876); -path.quadTo(5.85786438, 0, 0, 0); -path.close(); + +<div id="testSimplifyQuadratic4"> + SkPath path, out; + path.moveTo(0, 20); + path.quadTo(20, 0, 40, 20); + path.close(); + path.moveTo(40, 10); + path.quadTo(20, 30, 0, 10); + path.close(); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +} </div> -<div id="test_6div"> -original: -path.moveTo(0, 20); -path.quadTo(20, 0, 40, 20); -path.lineTo(0, 20); -path.close(); -path.moveTo(40, 10); -path.quadTo(20, 30, 0, 10); -path.lineTo(40, 10); -path.close(); -simplified: -path.moveTo(0, 10); -path.quadTo(2.92893219, 12.9289322, 5.85786438, 15); -path.quadTo(2.92893219, 17.0710678, 0, 20); -path.lineTo(40, 20); -path.quadTo(37.0710678, 17.0710678, 34.1421356, 15); -path.quadTo(37.0710678, 12.9289322, 40, 10); -path.lineTo(0, 10); -path.close(); + +<div id="testSimplifyQuadratic5"> + SkPath path, out; + path.moveTo(0, 0); + path.quadTo(0, 0, 0, 0); + path.lineTo(0, 0); + path.close(); + path.moveTo(0, 0); + path.lineTo(0, 0); + path.quadTo(0, 0, 0, 1); + path.close(); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +} </div> -<div id="test_7div"> + +<div id="testSimplifyQuadratic6"> + SkPath path, out; path.moveTo(0, 0); path.quadTo(0, 0, 0, 0); + path.lineTo(1, 0); + path.close(); + path.moveTo(0, 0); path.lineTo(0, 0); + path.quadTo(1, 0, 0, 1); path.close(); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +} +</div> + +<div id="testSimplifyQuadratic7"> + SkPath path, out; path.moveTo(0, 0); + path.quadTo(0, 0, 0, 0); path.lineTo(0, 1); - path.quadTo(1, 1, 1, 2); + path.close(); + path.moveTo(0, 0); path.lineTo(0, 0); + path.quadTo(1, 0, 0, 2); path.close(); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +} </div> -<div id="test_8div"> -original: -path.moveTo(0, 0); -path.lineTo(3, 1); -path.lineTo(0, 0); -path.close(); -path.moveTo(1, 0); -path.lineTo(0, 1); -path.quadTo(2, 1, 3, 3); -path.lineTo(1, 0); -path.close(); -simplified: -path.moveTo(1, 0); -path.lineTo(0, 1); -path.quadTo(1.42857146, 1, 2.34693885, 2.02040815); -path.lineTo(1.28571427, 0.428571433); -path.lineTo(1, 0); -path.close(); -path.moveTo(2.34693885, 2.02040815); -path.quadTo(2.71428561, 2.42857146, 3, 3); -path.lineTo(2.34693885, 2.02040815); -path.close(); +<div id="testSimplifyQuadratic8"> + SkPath path, out; + path.moveTo(0, 0); + path.quadTo(0, 0, 0, 0); + path.lineTo(0, 0); + path.close(); + path.moveTo(0, 0); + path.lineTo(0, 0); + path.quadTo(1, 0, 0, 2); + path.close(); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +} </div> -<div id="test_9div"> +<div id="testSimplifyQuadratic9"> + SkPath path, out; path.moveTo(0, 0); path.quadTo(0, 0, 0, 0); - path.lineTo(0, 2); + path.lineTo(1, 1); + path.close(); + path.moveTo(0, 0); path.lineTo(0, 0); + path.quadTo(1, 0, 2, 2); + path.close(); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +} +</div> + +<div id="testSimplifyQuadratic10"> + SkPath path, out; + path.moveTo(0, 0); + path.quadTo(0, 0, 0, 0); + path.lineTo(0, 0); + path.close(); + path.moveTo(0, 0); + path.lineTo(0, 1); + path.quadTo(1, 1, 1, 2); + path.close(); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +} +</div> + +<div id="testSimplifyQuadratic11"> + SkPath path, out; + path.moveTo(0, 0); + path.quadTo(0, 0, 0, 0); + path.lineTo(0, 2); path.close(); path.moveTo(0, 0); path.lineTo(2, 1); path.quadTo(2, 2, 3, 3); - path.lineTo(0, 0); path.close(); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +} </div> -<div id="test_10div"> -original: -path.moveTo(0, 0); -path.lineTo(0, 2); -path.lineTo(0, 0); -path.close(); -path.moveTo(3, 0); -path.quadTo(1, 1, 0, 2); -path.lineTo(3, 0); -path.close(); -simplified: -path.moveTo(0, 0); -path.lineTo(0, 2); -path.quadTo(1, 1, 3, 0); -path.lineTo(0, 0); -path.close(); +<div id="testSimplifyQuadratic12"> + SkPath path, out; + path.moveTo(0, 0); + path.lineTo(0, 2); + path.lineTo(0, 0); + path.close(); + path.moveTo(3, 0); + path.quadTo(1, 1, 0, 2); + path.lineTo(3, 0); + path.close(); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +} </div> -<div id="test_11div"> -original: +<div id="testSimplifyQuadratic13"> + SkPath path, out; path.moveTo(0, 0); path.quadTo(0, 0, 1, 0); path.lineTo(1, 1); @@ -188,56 +182,69 @@ path.moveTo(0, 0); path.quadTo(3, 0, 1, 1); path.lineTo(0, 0); path.close(); -simplified: -path.moveTo(0, 0); -path.lineTo(1, 1); -path.lineTo(1, 0); -path.lineTo(0, 0); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +} </div> -<div id="test_12div"> +<div id="testSimplifyQuadratic14"> + SkPath path, out; path.moveTo(0, 0); path.quadTo(0, 0, 0, 0); path.lineTo(1, 1); - path.lineTo(0, 0); path.close(); path.moveTo(0, 0); path.lineTo(0, 0); path.quadTo(0, 1, 2, 1); + path.close(); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +} +</div> + +<div id="testSimplifyQuadratic15"> + SkPath path, out; + path.moveTo(0, 0); + path.quadTo(0, 0, 1, 3); + path.lineTo(3, 3); + path.close(); + path.moveTo(0, 1); + path.lineTo(1, 1); + path.quadTo(0, 3, 3, 3); + path.close(); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +} +</div> + +<div id="testSimplifyQuadratic16"> + SkPath path, out; + path.moveTo(0, 0); + path.quadTo(0, 0, 0, 0); + path.lineTo(0, 1); + path.close(); + path.moveTo(0, 0); path.lineTo(0, 0); + path.quadTo(1, 0, 0, 1); path.close(); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +} </div> -<div id="test_13div"> -original: -path.moveTo(0, 0); -path.quadTo(0, 0, 1, 3); -path.lineTo(3, 3); -path.lineTo(0, 0); -path.close(); -path.moveTo(0, 1); -path.lineTo(1, 1); -path.quadTo(0, 3, 3, 3); -path.lineTo(0, 1); -path.close(); -simplified: -path.moveTo(0, 0); -path.lineTo(0.333333343, 1); -path.lineTo(0, 1); -path.lineTo(0.428571433, 1.28571427); -path.lineTo(0.333333343, 1); -path.lineTo(1, 1); -path.lineTo(0, 0); -path.close(); -path.moveTo(1, 1); -path.quadTo(0.857142866, 1.28571427, 0.795918345, 1.53061223); -path.lineTo(0.428571433, 1.28571427); -path.lineTo(1, 3); -path.lineTo(3, 3); -path.lineTo(0.795918345, 1.53061223); -path.quadTo(0.428571433, 3, 3, 3); -path.lineTo(1, 1); -path.close(); +<div id="testSimplifyQuadratic17"> + SkPath path, out; + path.moveTo(0, 0); + path.quadTo(0, 0, 0, 0); + path.lineTo(2, 2); + path.close(); + path.moveTo(0, 1); + path.lineTo(0, 1); + path.quadTo(2, 1, 3, 3); + path.close(); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +} </div> </div> @@ -245,19 +252,23 @@ path.close(); <script type="text/javascript"> var testDivs = [ - test_13div, - test_12div, - test_11div, - test_10div, - test_9div, - test_8div, - test_7div, - test_6div, - test_5div, - test_4div, - test_3div, - test_2div, - test_1div, + testSimplifyQuadratic17, + testSimplifyQuadratic16, + testSimplifyQuadratic15, + testSimplifyQuadratic14, + testSimplifyQuadratic13, + testSimplifyQuadratic12, + testSimplifyQuadratic11, + testSimplifyQuadratic10, + testSimplifyQuadratic9, + testSimplifyQuadratic8, + testSimplifyQuadratic7, + testSimplifyQuadratic6, + testSimplifyQuadratic5, + testSimplifyQuadratic4, + testSimplifyQuadratic3, + testSimplifyQuadratic2, + testSimplifyQuadratic1, ]; var scale, columns, rows, xStart, yStart; @@ -291,8 +302,18 @@ function parse(test) { if (pts.length > 0) verbs.push(pts); } - if (verbs.length > 0) + if (verbs.length > 0) { + var lastIndex = verbs.length - 1; + var lastVerb = verbs[lastIndex]; + var lastLen = lastVerb.length; + if (verbs[0][0] != lastVerb[lastLen - 2] && verbs[0][1] != lastVerb[lastLen - 1]) { + var lastPts = []; + lastPts.push(verbs[0][0]); + lastPts.push(verbs[0][1]); + verbs.push(lastPts); + } contours.push(verbs); + } } if (contours.length > 0) tests.push(contours); @@ -469,23 +490,24 @@ function doKeyPress(evt) { case 'n': if (++testIndex >= tests.length) testIndex = 0; - // insetScale = scale; + mouseX = Infinity; + drawTop(); + break; + case 'P': + case 'p': + if (--testIndex < 0) + testIndex = tests.length - 1; mouseX = Infinity; drawTop(); break; case 'T': case 't': - // drawTs(testIndex); break; case '-': - // if (--insetScale < 1) - // insetScale = 1; - // else redraw(); break; case '=': case '+': - // ++insetScale; redraw(); break; } |