work in progress
authorcaryclark@google.com <caryclark@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 7 Feb 2012 22:10:51 +0000 (22:10 +0000)
committercaryclark@google.com <caryclark@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 7 Feb 2012 22:10:51 +0000 (22:10 +0000)
git-svn-id: http://skia.googlecode.com/svn/trunk@3151 2bbb7eff-a529-9590-31e7-b0007b416f81

experimental/Intersection/CurveIntersection.h
experimental/Intersection/DataTypes.cpp
experimental/Intersection/EdgeWalker.cpp
experimental/Intersection/LineUtilities.cpp

index d6ca83788149ffaa3353188f39cf6f603c3712fc..f95e028632beacfc34a751dc38822d7031131ece 100644 (file)
@@ -13,7 +13,7 @@ bool implicit_matches(const Cubic& cubic1, const Cubic& cubic2);
 bool implicit_matches(const _Line& line1, const _Line& line2);
 bool implicit_matches(const Quadratic& quad1, const Quadratic& quad2);
 void sub_divide(const Cubic& src, double t1, double t2, Cubic& dst);
-void sub_divide(const _Line& src, double t1, double t2, Cubic& dst);
+void sub_divide(const _Line& src, double t1, double t2, _Line& dst);
 void sub_divide(const Quadratic& src, double t1, double t2, Quadratic& dst);
 void tangent(const Cubic& cubic, double t, _Point& result);
 void tangent(const _Line& line, _Point& result);
index 8f46e8e1cd52900274cc7100a35231ce8b32d718..07e7548b1bbc60987984516bed706c5bc66b7e3b 100644 (file)
@@ -81,14 +81,22 @@ void xy_at_t(const Cubic& cubic, double t, double& x, double& y) {
     double t2 = t * t;
     double c = 3 * one_t * t2;
     double d = t2 * t;
-    x = a * cubic[0].x + b * cubic[1].x + c * cubic[2].x + d * cubic[3].x;
-    y = a * cubic[0].y + b * cubic[1].y + c * cubic[2].y + d * cubic[3].y;
+    if (&x) {
+        x = a * cubic[0].x + b * cubic[1].x + c * cubic[2].x + d * cubic[3].x;
+    }
+    if (&y) {
+        y = a * cubic[0].y + b * cubic[1].y + c * cubic[2].y + d * cubic[3].y;
+    }
 }
 
 void xy_at_t(const _Line& line, double t, double& x, double& y) {
     double one_t = 1 - t;
-    x = one_t * line[0].x + t * line[1].x;
-    y = one_t * line[0].y + t * line[1].y;
+    if (&x) {
+        x = one_t * line[0].x + t * line[1].x;
+    }
+    if (&y) {
+        y = one_t * line[0].y + t * line[1].y;
+    }
 }
 
 void xy_at_t(const Quadratic& quad, double t, double& x, double& y) {
@@ -96,6 +104,10 @@ void xy_at_t(const Quadratic& quad, double t, double& x, double& y) {
     double a = one_t * one_t;
     double b = 2 * one_t * t;
     double c = t * t;
-    x = a * quad[0].x + b * quad[1].x + c * quad[2].x;
-    y = a * quad[0].y + b * quad[1].y + c * quad[2].y;
+    if (&x) {
+        x = a * quad[0].x + b * quad[1].x + c * quad[2].x;
+    }
+    if (&y) {
+        y = a * quad[0].y + b * quad[1].y + c * quad[2].y;
+    }
 }
index bf090e7bd0bd0f7e6475d52bb72e9d286cfdb3ab..4f4fc9b94d1f270254c5d920f1c70b193a52fd1a 100644 (file)
@@ -6,6 +6,7 @@
  * found in the LICENSE file.
  */
 
+#include "CurveIntersection.h"
 #include "LineIntersection.h"
 #include "SkPath.h"
 #include "SkRect.h"
 #include "SkTDArray.h"
 #include "TSearch.h"
 
-static int lineIntersect(const SkPoint a[2], const SkPoint b[2],
+static int LineIntersect(const SkPoint a[2], const SkPoint b[2],
         double aRange[2], double bRange[2]) {
     _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
     _Line bLine = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}};
     return intersect(aLine, bLine, aRange, bRange);
 }
 
-static int lineIntersect(const SkPoint a[2], SkScalar y, double aRange[2]) {
+static int LineIntersect(const SkPoint a[2], SkScalar y, double aRange[2]) {
     _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
     return horizontalIntersect(aLine, y, aRange);
 }
 
+static SkScalar LineYAtT(const SkPoint a[2], double t) {
+    _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 void LineSubDivide(const SkPoint a[2], double startT, double endT,
+        SkPoint sub[2]) {
+    _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);
+}
+
+
 // functions
 void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray);
 void simplify(const SkPath& path, bool asFill, SkPath& simple);
@@ -84,6 +104,29 @@ void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray) {
     }
 }
 
+struct OutEdge {
+
+    SkTDArray<SkPoint> fPts;
+    SkTDArray<uint8_t> fVerbs;
+};
+
+class OutEdgeBuilder {
+public:
+    void addLine(SkPoint pts[2]) {
+        ;
+        OutEdge* edge;
+        
+        edge = fEdges.append();
+        
+        if (empty) {
+            *edge->fPts.append() = pts[0];
+        }
+        *edge->fPts.append() = pts[1];
+    }
+
+    SkTArray<OutEdge> fEdges;
+};
+
 // 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) {
@@ -92,14 +135,12 @@ struct Bounds : public SkRect {
     }
 };
 
-struct Edge;
-
 struct Intercepts {
     SkTDArray<double> fTs;
 };
 
-struct Edge {
-    bool operator<(const Edge& rh) const {
+struct InEdge {
+    bool operator<(const InEdge& rh) const {
         return fBounds.fTop == rh.fBounds.fTop
                 ? fBounds.fLeft < rh.fBounds.fLeft
                 : fBounds.fTop < rh.fBounds.fTop;
@@ -125,7 +166,7 @@ struct Edge {
         }
     }
 
-    bool cached(const Edge* edge) {
+    bool cached(const InEdge* edge) {
         // FIXME: in the pathological case where there is a ton of edges, binary search?
         size_t count = fCached.count();
         for (size_t index = 0; index < count; ++index) {
@@ -157,25 +198,36 @@ struct Edge {
             ++ptPtr;
         }
         fIntercepts.push_back_n(1);
-        fWinding = winding;
+        if ((fWinding = winding) < 0) { // reverse verbs, pts, if bottom to top
+            size_t index;
+            size_t last = fPts.count() - 1;
+            for (index = 0; index < last; ++index, --last) {
+                SkTSwap<SkPoint>(fPts[index], fPts[last]);
+            }
+            last = fVerbs.count() - 1;
+            for (index = 0; index < last; ++index, --last) {
+                SkTSwap<uint8_t>(fVerbs[index], fVerbs[last]);
+            }
+        }
+        fContainsIntercepts = false;
     }
 
     // temporary data : move this to a separate struct?
-    SkTDArray<const Edge*> fCached; // list of edges already intercepted
+    SkTDArray<const InEdge*> fCached; // list of edges already intercepted
     SkTArray<Intercepts> fIntercepts; // one per verb
     
-
     // persistent data
     SkTDArray<SkPoint> fPts;
     SkTDArray<uint8_t> fVerbs;
     Bounds fBounds;
     signed char fWinding;
+    bool fContainsIntercepts;
 };
 
-class EdgeBuilder {
+class InEdgeBuilder {
 public:
 
-EdgeBuilder(const SkPath& path, bool ignoreHorizontal, SkTArray<Edge>& edges) 
+InEdgeBuilder(const SkPath& path, bool ignoreHorizontal, SkTArray<InEdge>& edges) 
     : fPath(path)
     , fCurrentEdge(NULL)
     , fEdges(edges)
@@ -200,8 +252,8 @@ int direction(int count) {
     }
     int last = count - 1;
     return fPts[0].fY == fPts[last].fY
-            ? fPts[0].fX == fPts[last].fX ? 0 : fPts[0].fX > fPts[last].fX
-            ? 1 : -1 : fPts[0].fY > fPts[last].fY ? 1 : -1;
+            ? fPts[0].fX == fPts[last].fX ? 0 : fPts[0].fX < fPts[last].fX
+            ? 1 : -1 : fPts[0].fY < fPts[last].fY ? 1 : -1;
 }
 
 bool isHorizontal() {
@@ -264,8 +316,8 @@ void walk() {
 
 private:
     const SkPath& fPath;
-    Edge* fCurrentEdge;
-    SkTArray<Edge>& fEdges;
+    InEdge* fCurrentEdge;
+    SkTArray<InEdge>& fEdges;
     SkPoint fPts[4];
     SkPath::Verb fVerb;
     int fPtCount;
@@ -275,41 +327,21 @@ private:
     bool fIgnoreHorizontal;
 };
 
-class WorkEdge {
-public:
-    WorkEdge(const Edge* edge) {
-        fVerbStart = edge->fVerbs.begin();
-        if ((fWinding = edge->fWinding) > 0) {
-            fPts = edge->fPts.begin();
-            fVerb = fVerbStart;
-            fVerbEnd = edge->fVerbs.end();
-        } else {
-            fPts = edge->fPts.end();
-            fVerb = edge->fVerbs.end();
-            fVerbEnd = fVerbStart;
-            next();
-        }
-    }
-
+struct WorkEdge {
     SkScalar bottom() const {
-        return fPts[fWinding > 0 ? verb() : 0].fY;
+        return fPts[verb()].fY;
     }
 
-    bool next() {
-        if (fWinding > 0) {
-            fPts += *fVerb;
-            return ++fVerb != fVerbEnd;
-        } else {
-            if (fVerb == fVerbEnd) {
-                return false;
-            }
-            fPts -= *--fVerb;
-            return true; 
-        }
+    void init(const InEdge* edge) {
+        fEdge = edge;
+        fPts = edge->fPts.begin();
+        fVerb = edge->fVerbs.begin();
     }
 
-    const SkPoint* points() const {
-        return fPts;
+    bool next() {
+        SkASSERT(fVerb < fEdge->fVerbs.end());
+        fPts += *fVerb++;
+        return fVerb != fEdge->fVerbs.end();
     }
 
     SkPath::Verb verb() const {
@@ -317,51 +349,82 @@ public:
     }
 
     int verbIndex() const {
-        return fVerb - fVerbStart;
+        return fVerb - fEdge->fVerbs.begin();
+    }
+    
+    int winding() const {
+        return fEdge->fWinding;
     }
 
-protected:     
+    const InEdge* fEdge;
     const SkPoint* fPts;
     const uint8_t* fVerb;
-    const uint8_t* fVerbEnd;
-    const uint8_t* fVerbStart;
-    int8_t fWinding;
 };
 
+// always constructed with SkTDArray because new edges are inserted
+// this may be a inappropriate optimization, suggesting that a separate array of
+// ActiveEdge* may be faster to insert and search
 struct ActiveEdge {
-    void init(const Edge* test) {
-        fEdge = test;
-        if (!fEdge->fIntercepts.count()) {
-            fBounds = test->fBounds;
-            fPtStart = 0;
-            fPtEnd = test->fPts.count();
-            fVerbStart = 0;
-            fVerbEnd = test->fVerbs.count();
-            fTStart = 0;
-            fTEnd = SK_Scalar1;
-        } else {
-            // FIXME: initialize from intercepts
+    void init(const InEdge* edge) {
+        fWorkEdge.init(edge);
+        initT();
+    }
+    
+    void initT() {
+        fTs = &fWorkEdge.fEdge->fIntercepts[fWorkEdge.verbIndex()].fTs;
+        fTIndex = 0;
+    }
+
+    bool nextT() {
+        SkASSERT(fTIndex <= fTs->count());
+        return ++fTIndex == fTs->count() + 1;
+    }
+    
+    bool next() {
+        bool result = fWorkEdge.next();
+        initT();
+        return result;
+    }
 
+    double t() {
+        if (fTIndex == 0) {
+            return 0;
+        }
+        if (fTIndex > fTs->count()) {
+            return 1;
         }
+        return (*fTs)[fTIndex - 1];
     }
 
-    const Edge* fEdge;
-    SkRect fBounds;
-    int fPtStart;
-    int fPtEnd;
-    int fVerbStart;
-    int fVerbEnd;
-    SkScalar fTStart;
-    SkScalar fTEnd;
+    WorkEdge fWorkEdge;
+    const SkTDArray<double>* fTs;
+    int fTIndex;
 };
 
+static void addToActive(SkTDArray<ActiveEdge>& activeEdges, const InEdge* edge) {
+    // 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) {
+            ActiveEdge* active = activeEdges.insert(index);
+            active->init(edge);
+            return;
+        }
+        if (edge == activeEdges[index].fWorkEdge.fEdge) {
+            return;
+        }
+    }
+    ActiveEdge* active = activeEdges.append();
+    active->init(edge);
+}
+
 void simplify(const SkPath& path, bool asFill, SkPath& simple) {
     // 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<Edge> edges;
-    EdgeBuilder builder(path, asFill, edges);
+    SkTArray<InEdge> edges;
+    InEdgeBuilder builder(path, asFill, edges);
     size_t edgeCount = edges.count();
     simple.reset();
     if (edgeCount == 0) {
@@ -369,24 +432,26 @@ 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;
-    SkTDArray<Edge*> edgeList;
+    SkTDArray<InEdge*> edgeList;
     for (size_t index = 0; index < edgeCount; ++index) {
         *edgeList.append() = &edges[index];
     }
-    Edge edgeSentinel;
+    InEdge edgeSentinel;
     edgeSentinel.fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
     *edgeList.append() = &edgeSentinel;
     ++edgeCount;
-    QSort<Edge>(edgeList.begin(), edgeCount);
-    Edge** currentPtr = edgeList.begin();
-    Edge* current = *currentPtr;
+    QSort<InEdge>(edgeList.begin(), edgeCount);
+    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;
     do {
         // find the list of edges that cross y
-        Edge** lastPtr = currentPtr; // find the edge below the bottom of the first set
-        Edge* last = *lastPtr;
+        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;
@@ -403,6 +468,7 @@ void simplify(const SkPath& path, bool asFill, SkPath& simple) {
             } else if (bottom > last->fBounds.fBottom) {
                 bottom = last->fBounds.fBottom;
             }
+            addToActive(activeEdges, last);
             last = *++lastPtr;
         }
         if (asFill && lastPtr - currentPtr <= 1) {
@@ -410,17 +476,21 @@ void simplify(const SkPath& path, bool asFill, SkPath& simple) {
             SkASSERT(0);
             return;
         }
+
         // find any intersections in the range of active edges
-        Edge** testPtr = currentPtr;
-        Edge* test = *testPtr;
+        InEdge** testPtr = currentPtr;
+        InEdge* test = *testPtr;
         while (testPtr != lastPtr) {
             if (test->fBounds.fBottom > bottom) {
-                WorkEdge wt(test);
+                WorkEdge wt;
+                wt.init(test);
                 do {
-                    // FIXME: add all combinations of curve types
+                    // 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.points(), bottom, wtTs);
+                        int pts = LineIntersect(wt.fPts, bottom, wtTs);
                         if (pts) {
                             test->add(wtTs, pts, wt.verbIndex());
                         }
@@ -432,47 +502,106 @@ void simplify(const SkPath& path, bool asFill, SkPath& simple) {
         testPtr = currentPtr;
         test = *testPtr;
         while (testPtr != lastPtr - 1) {
-            Edge* next = *++testPtr;
-            // OPTIMIZATION: if test and next is inside the winding of outer
-            // edges such that intersecting them is irrelevent, skip them.
+            InEdge* next = *++testPtr;
             if (!test->cached(next)
                     && Bounds::Intersects(test->fBounds, next->fBounds)) {
-                WorkEdge wt(test);
-                WorkEdge wn(next);
+                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) {
+                    if (wt.verb() == SkPath::kLine_Verb
+                            && wn.verb() == SkPath::kLine_Verb) {
                         double wtTs[2], wnTs[2];
-                        int pts = lineIntersect(wt.points(), wn.points(), wtTs, wnTs);
+                        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;
-        testPtr = currentPtr;
-        test = *testPtr;
-        while (testPtr != lastPtr - 1) {
+        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 += test->fWinding;
-            if ((lastWinding & windingMask) == 0 || (winding & windingMask) == 0) {
-                // append pts, verbs, in front of or behind output
-                // a verb may have one or more inter-T value, but only break
-                // curve if curve at t changes winding inclusion
-                ;
+            winding += wt.winding();
+            if (!(lastWinding & windingMask) && !(winding & windingMask)) {
+                continue;
             }
-            test = *++testPtr;
+            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:
+            ;
         }
+
         y = bottom;
-        while ((*currentPtr)->fBounds.fBottom >= y) {
+        while ((*currentPtr)->fBounds.fBottom <= y) {
             ++currentPtr;
         }
     } while (*currentPtr != &edgeSentinel);
+
     // assemble output path from string of pts, verbs
     ;
 }
index f70352a48fba1ac743b9d0bccdad5aa97d2fcd2f..13942252d91043e68a724bbd66d6c4a2abaffd0a 100644 (file)
@@ -21,3 +21,12 @@ int reduceOrder(const _Line& line, _Line& reduced) {
     reduced[1] = line[different];
     return 1 + different;
 }
+
+void sub_divide(const _Line& line, double t1, double t2, _Line& dst) {
+    _Point delta;
+    tangent(line, delta);
+    dst[0].x = line[0].x - t1 * delta.x;
+    dst[0].y = line[0].y - t1 * delta.y;
+    dst[1].x = line[0].x - t2 * delta.x;
+    dst[1].y = line[0].y - t2 * delta.y;
+}