work in progress
git-svn-id: http://skia.googlecode.com/svn/trunk@3159 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/experimental/Intersection/EdgeWalker.cpp b/experimental/Intersection/EdgeWalker.cpp
index 4f4fc9b..f3ae312 100644
--- a/experimental/Intersection/EdgeWalker.cpp
+++ b/experimental/Intersection/EdgeWalker.cpp
@@ -104,27 +104,208 @@
}
}
-struct OutEdge {
+static bool extendLine(const SkPoint line[2], const SkPoint& add) {
+ // FIXME: allow this to extend lines that have slopes that are nearly equal
+ SkScalar dx1 = line[1].fX - line[0].fX;
+ SkScalar dy1 = line[1].fY - line[0].fY;
+ SkScalar dx2 = add.fX - line[0].fX;
+ SkScalar dy2 = add.fY - line[0].fY;
+ return dx1 * dy2 == dx2 * dy1;
+}
+struct OutEdge {
+ bool operator<(const OutEdge& rh) const {
+ const SkPoint& first = fPts.begin()[0];
+ const SkPoint& rhFirst = rh.fPts.begin()[0];
+ return first.fY == rhFirst.fY
+ ? first.fX < rhFirst.fX
+ : first.fY < rhFirst.fY;
+ }
+
SkTDArray<SkPoint> fPts;
SkTDArray<uint8_t> fVerbs;
};
+// for sorting only
+class OutBottomEdge : public OutEdge {
+public:
+ bool operator<(const OutBottomEdge& rh) const {
+ const SkPoint& last = fPts.end()[-1];
+ const SkPoint& rhLast = rh.fPts.end()[-1];
+ return last.fY == rhLast.fY
+ ? last.fX < rhLast.fX
+ : last.fY < rhLast.fY;
+ }
+
+};
+
class OutEdgeBuilder {
public:
- void addLine(SkPoint pts[2]) {
- ;
- OutEdge* edge;
-
- edge = fEdges.append();
-
- if (empty) {
- *edge->fPts.append() = pts[0];
+ OutEdgeBuilder(bool fill)
+ : fFill(fill) {
}
- *edge->fPts.append() = pts[1];
+
+ void addLine(const SkPoint line[2]) {
+ size_t count = fEdges.count();
+ for (size_t index = 0; index < count; ++index) {
+ SkTDArray<SkPoint>& pts = fEdges[index].fPts;
+ SkPoint* last = pts.end() - 1;
+ if (last[0] == line[0]) {
+ if (extendLine(&last[-1], line[1])) {
+ last[0] = line[1];
+ } else {
+ *pts.append() = line[1];
+ }
+ return;
+ }
+ }
+ OutEdge& edge = fEdges.push_back();
+ *edge.fPts.append() = line[0];
+ *edge.fPts.append() = line[1];
+ }
+
+ void assemble(SkPath& simple) {
+ size_t index = 0;
+ do {
+ SkTDArray<SkPoint>& downArray = fEdges[index].fPts;
+ SkPoint* pts = downArray.begin();
+ SkPoints* end = downArray.end();
+ SkPoint firstPt = pts[0];
+ simple.moveTo(pts[0].fX, pts[0].fY);
+ while (++pts < end) {
+ simple.lineTo(pts->fX, pts->fY);
+ }
+ index = fBottoms[index];
+ SkTDArray<SkPoint>& upArray = fEdges[index].fPts;
+ pts = upArray.end();
+ SkPoints* begin = upArray.begin();
+ while (--pts > begin) {
+ simple.lineTo(pts->fX, pts->fY);
+ }
+ if (pts[0] == firstPt) {
+ simple.close();
+ closed = true;
+
+ } else {
+ simple.lineTo(pts->fX, pts->fY);
+ }
+ index = advance > 0 ? fBottoms[index] : fTops[index];
+ advance = -advance;
+ } while (true);
+
+ } else {
+ if (firstAdded.fY == pts[0].fY) {
+ advance = -1;
+ pts = ptArray.end();
+ }
+ }
+ size_t count2 = ptArray.count();
+ for (size_t inner = 1; inner < count2; ++inner) {
+ pts += advance;
+ simple.lineTo(pts->fX, pts->fY);
+ }
+ if (*pts == *ptArray.begin()) {
+// lastAdded = *pts;
+ simple.close();
+ newContour = true;
+ }
+ }
+ }
+
+ static bool lessThan(const SkTArray<OutEdge>& edges, const int* onePtr,
+ const int* twoPtr) {
+ int one = *onePtr;
+ const OutEdge& oneEdge = edges[(one < 0 ? -one : one) - 1];
+ const SkPoint* onePt = one < 0 ? oneEdge.fPts.begin()
+ : oneEdge.fPts.end() - 1;
+ int two = *twoPtr;
+ const OutEdge& twoEdge = edges[(two < 0 ? -two : two) - 1];
+ const SkPoint* twoPt = two < 0 ? twoEdge.fPts.begin()
+ : twoEdge.fPts.end() - 1;
+ return onePt.fY == twoPt.fY ? onePt.fX < twoPt.fX : onePt.fY < twoPt.fY;
+ }
+
+ void bridge() {
+ size_t index;
+ size_t count = fEdges.count();
+ if (!count) {
+ return;
+ }
+ SkASSERT(!fFill || (count & 1) == 0);
+ fTops.setCount(count);
+ sk_bzero(fTops.begin(), sizeof(fTops[0]) * count);
+ fBottoms.setCount(count);
+ sk_bzero(fBottoms.begin(), sizeof(fBottoms[0]) * count);
+ for (index = 0; index < count; ++index) {
+ *fList.append() = index + 1;
+ *fList.append() = -index - 1;
+ }
+ Context context;
+ QSort<SkTArray<OutEdge>&, int>(fEdges, fList.begin(), count, lessThan);
+ connectTops();
+ // sort bottoms
+ SkTDArray<OutBottomEdge*> bottomList;
+ for (index = 0; index < count; ++index) {
+ *bottomList.append() = static_cast<OutBottomEdge*>(&fEdges[index]);
+ fBottoms[index] = -1;
+ }
+ QSort<OutBottomEdge>(bottomList.begin(), count);
+ connectBottoms(bottomList);
+ }
+
+protected:
+ void connectTops() {
+ int* lastPtr = fList.end() - 1;
+ int* leftPtr = fList.begin();
+ for (; leftPtr < lastPtr; ++leftPtr) {
+ OutEdge* left = edges[(*leftPtr < 0 ? -*leftPtr : *leftPtr) - 1];
+ int* rightPtr = leftPtr + 1;
+ OutEdge* right = edges[(*rightPtr < 0 ? -*rightPtr : *rightPtr) - 1];
+ start here;
+ // i'm a bit confused right now -- but i'm trying to sort indices
+ // of paired points and then create more indices so assemble() can
+ // look up the next edge and whether to connect the top or bottom
+ int leftIndex = leftPtr - bottomList.begin();
+ int rightIndex = rightPtr - bottomList.begin();
+ SkASSERT(!fFill || left->fPts[0].fY == right->fPts[0].fY);
+ if (fFill || left->fPts[0] == right->fPts[0]) {
+ int leftIndex = leftPtr - topList.begin();
+ int rightIndex = rightPtr - topList.begin();
+ fTops[leftIndex] = rightIndex;
+ fTops[rightIndex] = leftIndex;
+ ++rightPtr;
+ }
+ leftPtr = rightPtr;
+ }
+ }
+
+ void connectBottoms(SkTDArray<OutBottomEdge*>& bottomList) {
+ OutBottomEdge** lastPtr = bottomList.end() - 1;
+ OutBottomEdge** leftPtr = bottomList.begin();
+ size_t leftCount = (*leftPtr)->fPts.count();
+ for (; leftPtr < lastPtr; ++leftPtr) {
+ OutBottomEdge** rightPtr = leftPtr + 1;
+ size_t rightCount = (*rightPtr)->fPts.count();
+ SkASSERT(!fFill || (*leftPtr)->fPts[leftCount].fY
+ == (*rightPtr)->fPts[rightCount].fY);
+ if (fFill || (*leftPtr)->fPts[leftCount]
+ == (*rightPtr)->fPts[rightCount]) {
+ int leftIndex = leftPtr - bottomList.begin();
+ int rightIndex = rightPtr - bottomList.begin();
+ fBottoms[leftIndex] = rightIndex;
+ fBottoms[rightIndex] = leftIndex;
+ if (++rightPtr < lastPtr) {
+ rightCount = (*rightPtr)->fPts.count();
+ }
+ }
+ leftPtr = rightPtr;
+ leftCount = rightCount;
+ }
}
SkTArray<OutEdge> fEdges;
+ SkTDArray<int> fList;
+ bool fFill;
};
// Bounds, unlike Rect, does not consider a vertical line to be empty.
@@ -239,6 +420,7 @@
protected:
void addEdge() {
+ SkASSERT(fCurrentEdge);
fCurrentEdge->fPts.append(fPtCount - fPtOffset, &fPts[fPtOffset]);
fPtOffset = 1;
*fCurrentEdge->fVerbs.append() = fVerb;
@@ -291,8 +473,10 @@
winding = direction(4);
break;
case SkPath::kClose_Verb:
+ SkASSERT(fCurrentEdge);
if (fCurrentEdge->fVerbs.count()) {
fCurrentEdge->complete(fWinding);
+ fCurrentEdge = NULL;
}
continue;
default:
@@ -305,13 +489,16 @@
if (fWinding + winding == 0) {
// FIXME: if prior verb or this verb is a horizontal line, reverse
// it instead of starting a new edge
+ SkASSERT(fCurrentEdge);
fCurrentEdge->complete(fWinding);
startEdge();
}
fWinding = winding;
addEdge();
}
- fCurrentEdge->complete(fWinding);
+ if (fCurrentEdge) {
+ fCurrentEdge->complete(fWinding);
+ }
}
private:
@@ -405,7 +592,7 @@
// FIXME: in the pathological case where there is a ton of intercepts, binary search?
size_t count = activeEdges.count();
for (size_t index = 0; index < count; ++index) {
- if (edge < activeEdges[index].fWorkEdge.fEdge) {
+ if (*edge < *activeEdges[index].fWorkEdge.fEdge) {
ActiveEdge* active = activeEdges.insert(index);
active->init(edge);
return;
@@ -418,192 +605,233 @@
active->init(edge);
}
+ // find any intersections in the range of active edges
+static void addBottomT(InEdge** currentPtr, InEdge** lastPtr, SkScalar bottom) {
+ InEdge** testPtr = currentPtr;
+ InEdge* test = *testPtr;
+ while (testPtr != lastPtr) {
+ if (test->fBounds.fBottom > bottom) {
+ WorkEdge wt;
+ wt.init(test);
+ do {
+ // FIXME: add all curve types
+ // OPTIMIZATION: if bottom intersection does not change
+ // the winding on either side of the split, don't intersect
+ if (wt.verb() == SkPath::kLine_Verb) {
+ double wtTs[2];
+ int pts = LineIntersect(wt.fPts, bottom, wtTs);
+ if (pts) {
+ test->add(wtTs, pts, wt.verbIndex());
+ }
+ }
+ } while (wt.next());
+ }
+ test = *++testPtr;
+ }
+}
+
+static void addIntersectingTs(InEdge** currentPtr, InEdge** lastPtr) {
+ InEdge** testPtr = currentPtr;
+ InEdge* test = *testPtr;
+ while (testPtr != lastPtr - 1) {
+ InEdge* next = *++testPtr;
+ if (!test->cached(next)
+ && Bounds::Intersects(test->fBounds, next->fBounds)) {
+ WorkEdge wt, wn;
+ wt.init(test);
+ wn.init(next);
+ do {
+ // FIXME: add all combinations of curve types
+ if (wt.verb() == SkPath::kLine_Verb
+ && wn.verb() == SkPath::kLine_Verb) {
+ double wtTs[2], wnTs[2];
+ int pts = LineIntersect(wt.fPts, wn.fPts, wtTs, wnTs);
+ if (pts) {
+ test->add(wtTs, pts, wt.verbIndex());
+ test->fContainsIntercepts = true;
+ next->add(wnTs, pts, wn.verbIndex());
+ next->fContainsIntercepts = true;
+ }
+ }
+ } while (wt.bottom() <= wn.bottom() ? wt.next() : wn.next());
+ }
+ test = next;
+ }
+}
+
+// compute bottom taking into account any intersected edges
+static void computeInterceptBottom(SkTDArray<ActiveEdge>& activeEdges,
+ SkScalar& bottom) {
+ ActiveEdge* activePtr = activeEdges.begin() - 1;
+ ActiveEdge* lastActive = activeEdges.end();
+ while (++activePtr != lastActive) {
+ const InEdge* test = activePtr->fWorkEdge.fEdge;
+ if (!test->fContainsIntercepts) {
+ continue;
+ }
+ WorkEdge wt;
+ wt.init(test);
+ do {
+ // FIXME: add all curve types
+ const Intercepts& intercepts = test->fIntercepts[wt.verbIndex()];
+ const SkTDArray<double>& fTs = intercepts.fTs;
+ size_t count = fTs.count();
+ for (size_t index = 0; index < count; ++index) {
+ if (wt.verb() == SkPath::kLine_Verb) {
+ SkScalar yIntercept = LineYAtT(wt.fPts, fTs[index]);
+ if (bottom > yIntercept) {
+ bottom = yIntercept;
+ }
+ }
+ }
+ } while (wt.next());
+ }
+}
+
+static SkScalar findBottom(InEdge** currentPtr,
+ InEdge** edgeListEnd, SkTDArray<ActiveEdge>& activeEdges, SkScalar y,
+ bool asFill, InEdge**& lastPtr) {
+ InEdge* current = *currentPtr;
+ SkScalar bottom = current->fBounds.fBottom;
+
+ // find the list of edges that cross y
+ InEdge* last = *lastPtr;
+ while (lastPtr != edgeListEnd) {
+ if (bottom <= last->fBounds.fTop) {
+ break;
+ }
+ SkScalar lastTop = last->fBounds.fTop;
+ // OPTIMIZATION: Shortening the bottom is only interesting when filling
+ // and when the edge is to the left of a longer edge. If it's a framing
+ // edge, or part of the right, it won't effect the longer edges.
+ if (lastTop > y) {
+ if (bottom > lastTop) {
+ bottom = lastTop;
+ break;
+ }
+ } else if (bottom > last->fBounds.fBottom) {
+ bottom = last->fBounds.fBottom;
+ }
+ addToActive(activeEdges, last);
+ last = *++lastPtr;
+ }
+ if (asFill && lastPtr - currentPtr <= 1) {
+ SkDebugf("expect 2 or more edges\n");
+ SkASSERT(0);
+ }
+ return bottom;
+}
+
+static void makeEdgeList(SkTArray<InEdge>& edges, InEdge& edgeSentinel,
+ SkTDArray<InEdge*>& edgeList) {
+ size_t edgeCount = edges.count();
+ if (edgeCount == 0) {
+ return;
+ }
+ for (size_t index = 0; index < edgeCount; ++index) {
+ *edgeList.append() = &edges[index];
+ }
+ edgeSentinel.fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
+ *edgeList.append() = &edgeSentinel;
+ ++edgeCount;
+ QSort<InEdge>(edgeList.begin(), edgeCount);
+}
+
+static void removeEdge(SkTDArray<ActiveEdge>& activeEdges, InEdge** currentPtr) {
+ InEdge* current = *currentPtr;
+ ActiveEdge* activePtr = activeEdges.begin() - 1;
+ ActiveEdge* lastActive = activeEdges.end();
+ while (++activePtr != lastActive) {
+ if (activePtr->fWorkEdge.fEdge == current) {
+ activeEdges.remove(activePtr - activeEdges.begin());
+ return;
+ }
+ }
+}
+
+// stitch edge and t range that satisfies operation
+static void stitchEdge(SkTDArray<ActiveEdge>& activeEdges, SkScalar y,
+ SkScalar bottom, int windingMask, OutEdgeBuilder& outBuilder) {
+ int winding = 0;
+ ActiveEdge* activePtr = activeEdges.begin() - 1;
+ ActiveEdge* lastActive = activeEdges.end();
+ SkDebugf("%s y=%g bottom=%g\n", __FUNCTION__, y, bottom);
+ while (++activePtr != lastActive) {
+ const WorkEdge& wt = activePtr->fWorkEdge;
+ int lastWinding = winding;
+ winding += wt.winding();
+ if (!(lastWinding & windingMask) && !(winding & windingMask)) {
+ continue;
+ }
+ do {
+ double currentT = activePtr->t();
+ const SkPoint* points = wt.fPts;
+ bool last;
+ do {
+ last = activePtr->nextT();
+ double nextT = activePtr->t();
+ // FIXME: add all combinations of curve types
+ if (wt.verb() == SkPath::kLine_Verb) {
+ SkPoint clippedPts[2];
+ const SkPoint* clipped;
+ if (currentT * nextT != 0 || currentT + nextT != 1) {
+ LineSubDivide(points, currentT, nextT, clippedPts);
+ clipped = clippedPts;
+ } else {
+ clipped = points;
+ }
+ SkDebugf("%s line %g,%g %g,%g\n", __FUNCTION__,
+ clipped[0].fX, clipped[0].fY,
+ clipped[1].fX, clipped[1].fY);
+ outBuilder.addLine(clipped);
+ if (clipped[1].fY >= bottom) {
+ goto nextEdge;
+ }
+ }
+ currentT = nextT;
+ } while (!last);
+ } while (activePtr->next());
+nextEdge:
+ ;
+ }
+}
+
void simplify(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 edges increasing in y
// if an edge is a quad or a cubic with a y extrema, note it, but leave it unbroken
// once we have a list, sort it, then walk the list (walk edges twice that have y extrema's on top)
// and detect crossings -- look for raw bounds that cross over, then tight bounds that cross
SkTArray<InEdge> edges;
InEdgeBuilder builder(path, asFill, edges);
- size_t edgeCount = edges.count();
- simple.reset();
- if (edgeCount == 0) {
- return;
- }
- // returns 1 for evenodd, -1 for winding, regardless of inverse-ness
- int windingMask = (path.getFillType() & 1) ? 1 : -1;
SkTDArray<InEdge*> edgeList;
- for (size_t index = 0; index < edgeCount; ++index) {
- *edgeList.append() = &edges[index];
- }
InEdge edgeSentinel;
- edgeSentinel.fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
- *edgeList.append() = &edgeSentinel;
- ++edgeCount;
- QSort<InEdge>(edgeList.begin(), edgeCount);
+ makeEdgeList(edges, edgeSentinel, edgeList);
InEdge** currentPtr = edgeList.begin();
- InEdge* current = *currentPtr;
- SkScalar y = current->fBounds.fTop;
- SkScalar bottom = current->fBounds.fBottom;
// walk the sorted edges from top to bottom, computing accumulated winding
SkTDArray<ActiveEdge> activeEdges;
- OutEdgeBuilder outBuilder;
+ OutEdgeBuilder outBuilder(asFill);
+ SkScalar y = (*currentPtr)->fBounds.fTop;
do {
- // find the list of edges that cross y
InEdge** lastPtr = currentPtr; // find the edge below the bottom of the first set
- InEdge* last = *lastPtr;
- while (lastPtr != edgeList.end()) {
- if (bottom <= last->fBounds.fTop) {
- break;
- }
- SkScalar lastTop = last->fBounds.fTop;
- // OPTIMIZATION: Shortening the bottom is only interesting when filling
- // and when the edge is to the left of a longer edge. If it's a framing
- // edge, or part of the right, it won't effect the longer edges.
- if (lastTop > y) {
- if (bottom > lastTop) {
- bottom = lastTop;
- break;
- }
- } else if (bottom > last->fBounds.fBottom) {
- bottom = last->fBounds.fBottom;
- }
- addToActive(activeEdges, last);
- last = *++lastPtr;
- }
- if (asFill && lastPtr - currentPtr <= 1) {
- SkDebugf("expect 2 or more edges\n");
- SkASSERT(0);
- return;
- }
-
- // find any intersections in the range of active edges
- InEdge** testPtr = currentPtr;
- InEdge* test = *testPtr;
- while (testPtr != lastPtr) {
- if (test->fBounds.fBottom > bottom) {
- WorkEdge wt;
- wt.init(test);
- do {
- // FIXME: add all curve types
- // OPTIMIZATION: if bottom intersection does not change
- // the winding on either side of the split, don't intersect
- if (wt.verb() == SkPath::kLine_Verb) {
- double wtTs[2];
- int pts = LineIntersect(wt.fPts, bottom, wtTs);
- if (pts) {
- test->add(wtTs, pts, wt.verbIndex());
- }
- }
- } while (wt.next());
- }
- test = *++testPtr;
- }
- testPtr = currentPtr;
- test = *testPtr;
- while (testPtr != lastPtr - 1) {
- InEdge* next = *++testPtr;
- if (!test->cached(next)
- && Bounds::Intersects(test->fBounds, next->fBounds)) {
- WorkEdge wt, wn;
- wt.init(test);
- wn.init(next);
- do {
- // FIXME: add all combinations of curve types
- if (wt.verb() == SkPath::kLine_Verb
- && wn.verb() == SkPath::kLine_Verb) {
- double wtTs[2], wnTs[2];
- int pts = LineIntersect(wt.fPts, wn.fPts, wtTs, wnTs);
- if (pts) {
- test->add(wtTs, pts, wt.verbIndex());
- test->fContainsIntercepts = true;
- next->add(wnTs, pts, wn.verbIndex());
- next->fContainsIntercepts = true;
- }
- }
- } while (wt.bottom() <= wn.bottom() ? wt.next() : wn.next());
- }
- test = next;
- }
-
- // compute bottom taking into account any intersected edges
- ActiveEdge* activePtr = activeEdges.begin() - 1;
- ActiveEdge* lastActive = activeEdges.end();
- while (++activePtr != lastActive) {
- const InEdge* test = activePtr->fWorkEdge.fEdge;
- if (!test->fContainsIntercepts) {
- continue;
- }
- WorkEdge wt;
- wt.init(test);
- do {
- // FIXME: add all curve types
- const Intercepts& intercepts = test->fIntercepts[wt.verbIndex()];
- const SkTDArray<double>& fTs = intercepts.fTs;
- size_t count = fTs.count();
- for (size_t index = 0; index < count; ++index) {
- if (wt.verb() == SkPath::kLine_Verb) {
- SkScalar yIntercept = LineYAtT(wt.fPts, fTs[index]);
- if (bottom > yIntercept) {
- bottom = yIntercept;
- }
- }
- }
- } while (wt.next());
- }
-
- // stitch edge and t range that satisfies operation
- int winding = 0;
- activePtr = activeEdges.begin() - 1;
- lastActive = activeEdges.end();
- SkDebugf("%s y=%g bottom=%g\n", __FUNCTION__, y, bottom);
- while (++activePtr != lastActive) {
- const WorkEdge& wt = activePtr->fWorkEdge;
- int lastWinding = winding;
- winding += wt.winding();
- if (!(lastWinding & windingMask) && !(winding & windingMask)) {
- continue;
- }
- do {
- double currentT = activePtr->t();
- const SkPoint* points = wt.fPts;
- bool last;
- do {
- last = activePtr->nextT();
- double nextT = activePtr->t();
- // FIXME: add all combinations of curve types
- if (wt.verb() == SkPath::kLine_Verb) {
- SkPoint clippedPts[2];
- const SkPoint* clipped;
- if (currentT * nextT != 0 || currentT + nextT != 1) {
- LineSubDivide(points, currentT, nextT, clippedPts);
- clipped = clippedPts;
- } else {
- clipped = points;
- }
- SkDebugf("%s line %g,%g %g,%g\n", __FUNCTION__,
- clipped[0].fX, clipped[0].fY,
- clipped[1].fX, clipped[1].fY);
- outBuilder->addLine(clipped);
- if (clipped[1].fY >= bottom) {
- goto nextEdge;
- }
- }
- currentT = nextT;
- } while (!last);
- } while (activePtr->next());
- nextEdge:
- ;
- }
-
+ SkScalar bottom = findBottom(currentPtr, edgeList.end(),
+ activeEdges, y, asFill, lastPtr);
+ addBottomT(currentPtr, lastPtr, bottom);
+ addIntersectingTs(currentPtr, lastPtr);
+ computeInterceptBottom(activeEdges, bottom);
+ stitchEdge(activeEdges, y, bottom, windingMask, outBuilder);
y = bottom;
while ((*currentPtr)->fBounds.fBottom <= y) {
+ removeEdge(activeEdges, currentPtr);
++currentPtr;
}
} while (*currentPtr != &edgeSentinel);
-
// assemble output path from string of pts, verbs
- ;
+ outBuilder.bridge();
+ outBuilder.assemble(simple);
}
void testSimplify();