shape ops work in progress
git-svn-id: http://skia.googlecode.com/svn/trunk@4006 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/experimental/Intersection/Simplify.cpp b/experimental/Intersection/Simplify.cpp
index cb2e3bc..444b32d 100644
--- a/experimental/Intersection/Simplify.cpp
+++ b/experimental/Intersection/Simplify.cpp
@@ -4,16 +4,7 @@
* 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"
-#include <algorithm> // used for std::min
+#include "Simplify.h"
#undef SkASSERT
#define SkASSERT(cond) while (!(cond)) { sk_throw(); }
@@ -21,8 +12,8 @@
// Terminology:
// A Path contains one of more Contours
// A Contour is made up of Segment array
-// A Segment is described by a Verb and a Point array
-// A Verb is one of Line, Quad(ratic), and Cubic
+// A Segment is described by a Verb and a Point array with 2, 3, or 4 points
+// A Verb is one of Line, Quad(ratic), or Cubic
// A Segment contains a Span array
// A Span is describes a portion of a Segment using starting and ending T
// T values range from 0 to 1, where 0 is the first Point in the Segment
@@ -263,6 +254,14 @@
sub[3].fY = SkDoubleToScalar(dst[3].y);
}
+static void (* const SegmentSubDivide[])(const SkPoint [], double , double ,
+ SkPoint []) = {
+ NULL,
+ LineSubDivide,
+ QuadSubDivide,
+ CubicSubDivide
+};
+
static void QuadSubBounds(const SkPoint a[3], double startT, double endT,
SkRect& bounds) {
SkPoint dst[3];
@@ -291,6 +290,9 @@
{a[2].fX, a[2].fY}};
Quadratic dst;
int order = reduceOrder(aQuad, dst);
+ if (order == 3) {
+ return SkPath::kQuad_Verb;
+ }
for (int index = 0; index < order; ++index) {
SkPoint* pt = reducePts.append();
pt->fX = SkDoubleToScalar(dst[index].x);
@@ -305,6 +307,9 @@
{a[2].fX, a[2].fY}, {a[3].fX, a[3].fY}};
Cubic dst;
int order = reduceOrder(aCubic, dst, kReduceOrder_QuadraticsAllowed);
+ if (order == 4) {
+ return SkPath::kCubic_Verb;
+ }
for (int index = 0; index < order; ++index) {
SkPoint* pt = reducePts.append();
pt->fX = SkDoubleToScalar(dst[index].x);
@@ -330,19 +335,19 @@
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;
+ return startT < endT ? (float) startT : (float) 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);
+ return (float) 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);
+ return (float) leftMostT(aCubic, startT, endT);
}
static SkScalar (* const SegmentLeftMost[])(const SkPoint [], double , double) = {
@@ -359,57 +364,166 @@
return implicit_matches_ulps(aLine, bLine, 32);
}
+class Segment;
+
// sorting angles
// given angles of {dx dy ddx ddy dddx dddy} sort them
class Angle {
public:
+ // FIXME: this is bogus for quads and cubics
+ // if the quads and cubics' line from end pt to ctrl pt are coincident,
+ // there's no obvious way to determine the curve ordering from the
+ // derivatives alone. In particular, if one quadratic's coincident tangent
+ // is longer than the other curve, the final control point can place the
+ // longer curve on either side of the shorter one.
+ // Using Bezier curve focus http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf
+ // may provide some help, but nothing has been figured out yet.
bool operator<(const Angle& rh) const {
- if ((dy < 0) ^ (rh.dy < 0)) {
- return dy < 0;
+ if ((fDy < 0) ^ (rh.fDy < 0)) {
+ return fDy < 0;
}
- SkScalar cmp = dx * rh.dy - rh.dx * dy;
+ if (fDy == 0 && rh.fDy == 0 && fDx != rh.fDx) {
+ return fDx < rh.fDx;
+ }
+ SkScalar cmp = fDx * rh.fDy - rh.fDx * fDy;
if (cmp) {
return cmp < 0;
}
- if ((ddy < 0) ^ (rh.ddy < 0)) {
- return ddy < 0;
+ if ((fDDy < 0) ^ (rh.fDDy < 0)) {
+ return fDDy < 0;
}
- cmp = ddx * rh.ddy - rh.ddx * ddy;
+ if (fDDy == 0 && rh.fDDy == 0 && fDDx != rh.fDDx) {
+ return fDDx < rh.fDDx;
+ }
+ cmp = fDDx * rh.fDDy - rh.fDDx * fDDy;
if (cmp) {
return cmp < 0;
}
- if ((dddy < 0) ^ (rh.dddy < 0)) {
- return ddy < 0;
+ if ((fDDDy < 0) ^ (rh.fDDDy < 0)) {
+ return fDDDy < 0;
}
- return dddx * rh.dddy < rh.dddx * dddy;
+ if (fDDDy == 0 && rh.fDDDy == 0) {
+ return fDDDx < rh.fDDDx;
+ }
+ return fDDDx * rh.fDDDy < rh.fDDDx * fDDDy;
}
- void set(SkPoint* pts, SkPath::Verb verb) {
- dx = pts[1].fX - pts[0].fX; // b - a
- dy = pts[1].fY - pts[0].fY;
+ int end() const {
+ return fEnd;
+ }
+
+ // since all angles share a point, this needs to know which point
+ // is the common origin, i.e., whether the center is at pts[0] or pts[verb]
+ // practically, this should only be called by addAngle
+ void set(const SkPoint* pts, SkPath::Verb verb, const Segment* segment,
+ int start, int end, bool coincident) {
+ SkASSERT(start != end);
+ fSegment = segment;
+ fStart = start;
+ fEnd = end;
+ fCoincident = coincident;
+ fDx = pts[1].fX - pts[0].fX; // b - a
+ fDy = pts[1].fY - pts[0].fY;
if (verb == SkPath::kLine_Verb) {
- ddx = ddy = dddx = dddy = 0;
+ fDDx = fDDy = fDDDx = fDDDy = 0;
return;
}
- ddx = pts[2].fX - pts[1].fX - dx; // a - 2b + c
- ddy = pts[2].fY - pts[2].fY - dy;
+ fDDx = pts[2].fX - pts[1].fX - fDx; // a - 2b + c
+ fDDy = pts[2].fY - pts[1].fY - fDy;
if (verb == SkPath::kQuad_Verb) {
- dddx = dddy = 0;
+ fDDDx = fDDDy = 0;
return;
}
- dddx = pts[3].fX + 3 * (pts[1].fX - pts[2].fX) - pts[0].fX;
- dddy = pts[3].fY + 3 * (pts[1].fY - pts[2].fY) - pts[0].fY;
+ fDDDx = pts[3].fX + 3 * (pts[1].fX - pts[2].fX) - pts[0].fX;
+ fDDDy = pts[3].fY + 3 * (pts[1].fY - pts[2].fY) - pts[0].fY;
+ }
+
+ // noncoincident quads/cubics may have the same initial angle
+ // as lines, so must sort by derivatives as well
+ // if flatness turns out to be a reasonable way to sort, use the below:
+ void setFlat(const SkPoint* pts, SkPath::Verb verb, const Segment* segment,
+ int start, int end, bool coincident) {
+ fSegment = segment;
+ fStart = start;
+ fEnd = end;
+ fCoincident = coincident;
+ fDx = pts[1].fX - pts[0].fX; // b - a
+ fDy = pts[1].fY - pts[0].fY;
+ if (verb == SkPath::kLine_Verb) {
+ fDDx = fDDy = fDDDx = fDDDy = 0;
+ return;
+ }
+ if (verb == SkPath::kQuad_Verb) {
+ int uplsX = FloatAsInt(pts[2].fX - pts[1].fY - fDx);
+ int uplsY = FloatAsInt(pts[2].fY - pts[1].fY - fDy);
+ int larger = std::max(abs(uplsX), abs(uplsY));
+ int shift = 0;
+ double flatT;
+ SkPoint ddPt; // FIXME: get rid of copy (change fDD_ to point)
+ LineParameters implicitLine;
+ _Line tangent = {{pts[0].fX, pts[0].fY}, {pts[1].fX, pts[1].fY}};
+ implicitLine.lineEndPoints(tangent);
+ implicitLine.normalize();
+ while (larger > UlpsEpsilon * 1024) {
+ larger >>= 2;
+ ++shift;
+ flatT = 0.5 / (1 << shift);
+ QuadXYAtT(pts, flatT, &ddPt);
+ _Point _pt = {ddPt.fX, ddPt.fY};
+ double distance = implicitLine.pointDistance(_pt);
+ if (approximately_zero(distance)) {
+ SkDebugf("%s ulps too small %1.9g\n", __FUNCTION__, distance);
+ break;
+ }
+ }
+ flatT = 0.5 / (1 << shift);
+ QuadXYAtT(pts, flatT, &ddPt);
+ fDDx = ddPt.fX - pts[0].fX;
+ fDDy = ddPt.fY - pts[0].fY;
+ SkASSERT(fDDx != 0 || fDDy != 0);
+ fDDDx = fDDDy = 0;
+ return;
+ }
+ SkASSERT(0); // FIXME: add cubic case
+ }
+
+ const Segment* segment() const {
+ return fSegment;
+ }
+
+ int sign() const {
+ int result = fStart - fEnd >> 31 | 1;
+ SkASSERT(result == fStart < fEnd ? -1 : 1);
+ return result;
+ }
+
+ int start() const {
+ return fStart;
}
private:
- SkScalar dx;
- SkScalar dy;
- SkScalar ddx;
- SkScalar ddy;
- SkScalar dddx;
- SkScalar dddy;
+ SkScalar fDx;
+ SkScalar fDy;
+ SkScalar fDDx;
+ SkScalar fDDy;
+ SkScalar fDDDx;
+ SkScalar fDDDy;
+ const Segment* fSegment;
+ int fStart;
+ int fEnd;
+ bool fCoincident;
};
+static void sortAngles(SkTDArray<Angle>& angles, SkTDArray<Angle*>& angleList) {
+ int angleCount = angles.count();
+ int angleIndex;
+ angleList.setReserve(angleCount);
+ for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
+ *angleList.append() = &angles[angleIndex];
+ }
+ QSort<Angle>(angleList.begin(), angleList.end() - 1);
+}
+
// 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) {
@@ -429,7 +543,8 @@
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);
+ set((float) dRect.left, (float) dRect.top, (float) dRect.right,
+ (float) dRect.bottom);
}
void setQuadBounds(const SkPoint a[3]) {
@@ -437,16 +552,16 @@
{a[2].fX, a[2].fY}};
_Rect dRect;
dRect.setBounds(quad);
- set(dRect.left, dRect.top, dRect.right, dRect.bottom);
+ set((float) dRect.left, (float) dRect.top, (float) dRect.right,
+ (float) dRect.bottom);
}
};
-class Segment;
-
struct Span {
double fT;
Segment* fOther;
- double fOtherT;
+ double fOtherT; // value at fOther[fOtherIndex].fT
+ int fOtherIndex; // can't be used during intersection
int fWinding; // accumulated from contours surrounding this one
// OPTIMIZATION: done needs only 2 bits (values are -1, 0, 1)
int fDone; // set when t to t+fDone is processed
@@ -462,31 +577,34 @@
#endif
}
- void addAngle(SkTDArray<Angle>& angles, double start, double end) {
- // FIXME complete this
- // start here;
+ void addAngle(SkTDArray<Angle>& angles, int start, int end,
+ bool coincident) const {
+ SkASSERT(start != end);
+ SkPoint edge[4];
+ (*SegmentSubDivide[fVerb])(fPts, fTs[start].fT, fTs[end].fT, edge);
+ Angle* angle = angles.append();
+ angle->set(edge, fVerb, this, start, end, coincident);
}
- bool addCubic(const SkPoint pts[4]) {
- fPts = pts;
- fVerb = SkPath::kCubic_Verb;
+ void addCubic(const SkPoint pts[4]) {
+ init(pts, SkPath::kCubic_Verb);
fBounds.setCubicBounds(pts);
}
- bool addLine(const SkPoint pts[2]) {
- fPts = pts;
- fVerb = SkPath::kLine_Verb;
+ void addLine(const SkPoint pts[2]) {
+ init(pts, 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) {
- fTs[index].fOtherT = other;
+ void addOtherT(int index, double otherT, int otherIndex) {
+ Span& span = fTs[index];
+ span.fOtherT = otherT;
+ span.fOtherIndex = otherIndex;
}
- bool addQuad(const SkPoint pts[3]) {
- fPts = pts;
- fVerb = SkPath::kQuad_Verb;
+ void addQuad(const SkPoint pts[3]) {
+ init(pts, SkPath::kQuad_Verb);
fBounds.setQuadBounds(pts);
}
@@ -522,10 +640,70 @@
return insertedAt;
}
+ void addTwoAngles(int start, int end, const SkPoint& endLoc,
+ const Span* endSpan, bool startCo, SkTDArray<Angle>& angles) const {
+ // add edge leading into junction
+ addAngle(angles, end, start, startCo);
+ // add edge leading away from junction
+ bool coincident;
+ int step = start < end ? 1 : -1;
+ int tIndex = nextSpan(end, step, endLoc, endSpan, NULL, coincident);
+ if (tIndex >= 0) {
+ lastSpan(tIndex, step, endLoc, endSpan, coincident);
+ addAngle(angles, end, tIndex, coincident);
+ }
+ }
+
const Bounds& bounds() const {
return fBounds;
}
+ void buildAngles(int index, int last, int step, const SkPoint& loc,
+ SkTDArray<Angle>& angles) const {
+ SkASSERT(index - last != 0);
+ SkASSERT((index - last < 0) ^ (step < 0));
+ int end = last + step;
+ do {
+ Span* span = &fTs[index];
+ Segment* other = span->fOther;
+ if (other->fDone) {
+ continue;
+ }
+ // if there is only one live crossing, and no coincidence, continue
+ // in the same direction
+ // if there is coincidence, the only choice may be to reverse direction
+ // find edge on either side of intersection
+ int oIndex = span->fOtherIndex;
+ Span* otherSpan = &other->fTs[oIndex];
+ SkASSERT(otherSpan->fOther == this);
+ // if done == -1, prior span has already been processed
+ bool otherCo;
+ int localStep = step;
+ int next = other->nextSpan(oIndex, localStep, loc, otherSpan,
+ NULL, otherCo);
+ if (next < 0) {
+ localStep = -step;
+ next = other->nextSpan(oIndex, localStep, loc, otherSpan,
+ NULL, otherCo);
+ }
+ other->lastSpan(next, localStep, loc, otherSpan, otherCo);
+ // add candidate into and away from junction
+ other->addTwoAngles(next, oIndex, loc, span, otherCo, angles);
+
+ } while ((index += step) != end);
+ }
+
+ // figure out if the segment's ascending T goes clockwise or not
+ // not enough context to write this as shown
+ // instead, add all segments meeting at the top
+ // sort them using buildAngleList
+ // find the first in the sort
+ // see if ascendingT goes to top
+ bool clockwise(int tIndex) const {
+ SkASSERT(0); // incomplete
+ return false;
+ }
+
bool done() const {
return fDone;
}
@@ -545,13 +723,13 @@
SkASSERT(0); // should never get here
return -1;
}
-
+
// start is the index of the beginning T of this edge
// it is guaranteed to have an end which describes a non-zero length (?)
// winding -1 means ccw, 1 means cw
// step is in/out -1 or 1
// spanIndex is returned
- Segment* findNext(int start, int winding, int& step, int& spanIndex) {
+ Segment* findNext(int start, int winding, int& step, int& spanIndex) const {
SkASSERT(step == 1 || step == -1);
int count = fTs.count();
SkASSERT(step > 0 ? start < count - 1 : start > 0);
@@ -565,129 +743,83 @@
SkPoint startLoc; // OPTIMIZATION: store this in the t span?
xyAtT(startSpan->fT, &startLoc);
SkPoint endLoc;
- Span* endSpan;
- int end = nextSpan(start, step, startLoc, startSpan, &endLoc, &endSpan);
+ bool startCo;
+ int end = nextSpan(start, step, startLoc, startSpan, &endLoc, startCo);
// if we hit the end looking for span end, is that always an error?
SkASSERT(step > 0 ? end + 1 < count : end - 1 >= 0);
// preflight for coincidence -- if present, it may change winding
// considerations and whether reversed edges can be followed
- bool foundCoincident = false;
- int last = lastSpan(end, step, &startLoc, startSpan, foundCoincident);
+ int last = lastSpan(end, step, startLoc, startSpan, startCo);
// Discard opposing direction candidates if no coincidence was found.
+ Span* endSpan = &fTs[end];
int candidateCount = abs(last - end);
+ Segment* other;
if (candidateCount == 1) {
- SkASSERT(!foundCoincident);
+ SkASSERT(!startCo);
// move in winding direction until edge in correct direction
// balance wrong direction edges before finding correct one
// this requres that the intersection is angularly sorted
// for a single intersection, special case -- choose the opposite
// edge that steps the same
- Segment* other = endSpan->fOther;
+ other = endSpan->fOther;
SkASSERT(!other->fDone);
- spanIndex = other->matchSpan(this, endSpan->fT);
- SkASSERT(step < 0 ? spanIndex > 0 : spanIndex < other->fTs.count() - 1);
+ spanIndex = endSpan->fOtherIndex;
+ SkASSERT(step < 0 ? spanIndex > 0
+ : spanIndex < other->fTs.count() - 1);
return other;
}
- // find the next T that describes a length
+ // more than one viable candidate -- measure angles to find best
SkTDArray<Angle> angles;
- Segment* segmentCandidate = NULL;
- int spanCandidate = -1;
- int directionCandidate;
- do {
- endSpan = &fTs[end];
- Segment* other = endSpan->fOther;
- if (other->fDone) {
- continue;
+ SkASSERT(end - start != 0);
+ SkASSERT((end - start < 0) ^ (step < 0));
+ addTwoAngles(start, end, endLoc, endSpan, startCo, angles);
+ buildAngles(end, last, step, endLoc, angles);
+ SkTDArray<Angle*> sorted;
+ sortAngles(angles, sorted);
+ // find the starting edge
+ int startIndex = -1;
+ int angleCount = angles.count();
+ int angleIndex;
+ const Angle* angle;
+ for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
+ angle = sorted[angleIndex];
+ if (angle->segment() == this && angle->start() == end &&
+ angle->end() == start) {
+ startIndex = angleIndex;
+ break;
}
- // if there is only one live crossing, and no coincidence, continue
- // in the same direction
- // if there is coincidence, the only choice may be to reverse direction
- // find edge on either side of intersection
- int oIndex = other->matchSpan(this, endSpan->fT);
- int oCount = other->fTs.count();
- do {
- Span& otherSpan = other->fTs[oIndex];
- // if done == -1, prior span has already been processed
- int next = other->nextSpan(oIndex, step, endLoc, &otherSpan,
- NULL, NULL);
- if (next < 0) {
- continue;
- }
- bool otherIsCoincident;
- last = other->lastSpan(next, step, &endLoc, &otherSpan,
- otherIsCoincident);
- if (last < 0) {
- continue;
- }
- #if 0
- Span& prior = other->fTs[oIndex - 1];
- if (otherSpan.fDone >= 0 && oIndex > 0) {
- // FIXME: this needs to loop on -- until t && pt are different
- if (prior.fDone > 0) {
- continue;
- }
-
- }
- } else { // step == 1
- if (otherSpan.fDone <= 0 && oIndex < oCount - 1) {
- // FIXME: this needs to loop on ++ until t && pt are different
- Span& next = other->fTs[oIndex + 1];
- if (next.fDone < 0) {
- continue;
- }
- }
- }
- #endif
- if (!segmentCandidate) {
- segmentCandidate = other;
- spanCandidate = oIndex;
- directionCandidate = step;
- continue;
- }
- // there's two or more matches
- if (spanCandidate >= 0) { // retrieve first stored candidate
- // add edge leading into junction
- addAngle(angles, endSpan->fT, startSpan->fT);
- // add edge leading away from junction
- double nextT = nextSpan(end, step, endLoc, endSpan, NULL,
- NULL);
- if (nextT >= 0) {
- addAngle(angles, endSpan->fT, nextT);
- }
- // add first stored candidate into junction
- segmentCandidate->addAngle(angles,
- segmentCandidate->fTs[spanCandidate - 1].fT,
- segmentCandidate->fTs[spanCandidate].fT);
- // add first stored candidate away from junction
- segmentCandidate->addAngle(angles,
- segmentCandidate->fTs[spanCandidate].fT,
- segmentCandidate->fTs[spanCandidate + 1].fT);
- }
- // add candidate into and away from junction
-
-
- // start here;
- // more than once viable candidate -- need to
- // measure angles to find best
- // noncoincident quads/cubics may have the same initial angle
- // as lines, so must sort by derivatives as well
- // while we're here, figure out all connections given the
- // initial winding info
- // so the span needs to contain the pairing info found here
- // this should include the winding computed for the edge, and
- // what edge it connects to, and whether it is discarded
- // (maybe discarded == abs(winding) > 1) ?
- // only need derivatives for duration of sorting, add a new struct
- // for pairings, remove extra spans that have zero length and
- // reference an unused other
- // for coincident, the last span on the other may be marked done
- // (always?)
- } while (++oIndex < oCount);
- } while ((end += step) != last);
+ }
+ SkASSERT(startIndex >= 0);
+ winding += angle->sign();
+ int nextIndex = startIndex;
+ const Angle* nextAngle;
+ do {
+ if (++nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ SkASSERT(nextIndex != startIndex); // should never wrap around
+ nextAngle = sorted[nextIndex];
+ // OPTIMIZATION: Figure out all connections, given the initial
+ // winding info (e.g., accumulate winding in span for reuse)
+ winding -= nextAngle->sign();
+ } while (winding);
+ // FIXME: get rid of cast
+ return const_cast<Segment*>(nextAngle->segment());
+
+ // so the span needs to contain the pairing info found here
+ // this should include the winding computed for the edge, and
+ // what edge it connects to, and whether it is discarded
+ // (maybe discarded == abs(winding) > 1) ?
+ // only need derivatives for duration of sorting, add a new struct
+ // for pairings, remove extra spans that have zero length and
+ // reference an unused other
+ // for coincident, the last span on the other may be marked done
+ // (always?)
+
// if loop is exhausted, contour may be closed.
// FIXME: pass in close point so we can check for closure
@@ -760,8 +892,7 @@
}
continue;
}
- if (toSpan.fOther == mOther && toSpan.fOtherT
- == moSpan.fT) {
+ if (toSpan.fOther == mOther && toSpan.fOtherT == moSpan.fT) {
found = true;
break;
}
@@ -786,28 +917,15 @@
}
}
- 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 Span& span = fTs[index];
- if (t == span.fT && match == span.fOther) {
- return index;
- }
- }
- SkASSERT(0); // should never get here
- return -1;
- }
-
// find the adjacent T that is leftmost, with a point != base
int findLefty(int tIndex, const SkPoint& base) const {
- int bestTIndex;
+ int bestTIndex = -1;
SkPoint test;
- SkScalar bestX = DBL_MAX;
+ SkScalar bestX = FLT_MAX;
int testTIndex = tIndex;
while (--testTIndex >= 0) {
- xyAtT(testTIndex, &test);
- if (test != base) {
+ xyAtT(fTs[testTIndex].fT, &test);
+ if (test == base) {
continue;
}
bestX = test.fX;
@@ -817,20 +935,23 @@
int count = fTs.count();
testTIndex = tIndex;
while (++testTIndex < count) {
- xyAtT(testTIndex, &test);
+ xyAtT(fTs[testTIndex].fT, &test);
if (test == base) {
continue;
}
- return bestX > test.fX ? testTIndex : bestTIndex;
+ if (bestX > test.fX) {
+ bestTIndex = testTIndex;
+ }
+ break;
}
- SkASSERT(0); // can't get here (?)
- return -1;
+ SkASSERT(bestTIndex != -1);
+ return bestTIndex;
}
// OPTIMIZATION : for a pair of lines, can we compute points at T (cached)
// and use more concise logic like the old edge walker code?
// FIXME: this needs to deal with coincident edges
- const Segment* findTop(int& tIndex) const {
+ const Segment* findTop(int& tIndex, int& direction) const {
// iterate through T intersections and return topmost
// topmost tangent from y-min to first pt is closer to horizontal
int firstT = 0;
@@ -841,7 +962,7 @@
for (index = 1; index < count; ++index) {
const Span& span = fTs[index];
double t = span.fT;
- SkScalar yIntercept = yAtT(t);
+ SkScalar yIntercept = t == 1 ? fPts[fVerb].fY : yAtT(t);
if (topY > yIntercept) {
topY = yIntercept;
firstT = lastT = index;
@@ -850,44 +971,65 @@
}
}
// if there's only a pair of segments, go with the endpoint chosen above
- if (firstT == lastT && (firstT == 0 || firstT == count - 1)) {
+ if (firstT == lastT) {
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);
- const Segment* leftSegment = this;
- // look for left-ness from tLeft to firstT (matching y of other)
- 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);
- // within this span, find highest y
- SkPoint testPt, otherPt;
- testPt.fY = yAtT(tLeft);
- otherPt.fY = other->yAtT(otherTLeft);
- // FIXME: incomplete
- // find the y intercept with the opposite segment
- if (testPt.fY < otherPt.fY) {
-
- } else if (testPt.fY > otherPt.fY) {
-
- }
- // FIXME: leftMost no good. Use y intercept instead
-#if 0
- SkScalar otherMost = other->leftMost(otherTIndex, otherTLeft);
- if (otherMost < left) {
- leftSegment = other;
- }
-#endif
+ // sort the edges to find the leftmost
+ SkPoint startLoc; // OPTIMIZATION: store this in the t span?
+ const Span* startSpan = &fTs[firstT];
+ xyAtT(startSpan->fT, &startLoc);
+ SkPoint endLoc;
+ bool nextCo;
+ int end = nextSpan(firstT, 1, startLoc, startSpan, &endLoc, nextCo);
+ if (end == -1) {
+ end = nextSpan(firstT, -1, startLoc, startSpan, &endLoc, nextCo);
}
+ // if the topmost T is not on end, or is three-way or more, find left
+ // look for left-ness from tLeft to firstT (matching y of other)
+ SkTDArray<Angle> angles;
+ SkASSERT(firstT - end != 0);
+ addTwoAngles(end, firstT, endLoc, &fTs[firstT], nextCo, angles);
+ buildAngles(firstT, lastT, 1, startLoc, angles);
+ SkTDArray<Angle*> sorted;
+ sortAngles(angles, sorted);
+ const Segment* leftSegment = sorted[0]->segment();
+ tIndex = sorted[0]->end();
+ direction = sorted[0]->start() - tIndex;
+ SkASSERT(direction);
+ direction = direction < 0 ? -1 : 1;
return leftSegment;
}
+ // FIXME: not crazy about this
+ // when the intersections are performed, the other index is into an
+ // incomplete array. as the array grows, the indices become incorrect
+ // while the following fixes the indices up again, it isn't smart about
+ // skipping segments whose indices are already correct
+ // assuming we leave the code that wrote the index in the first place
+ void fixOtherTIndex() {
+ int iCount = fTs.count();
+ for (int i = 0; i < iCount; ++i) {
+ Span& iSpan = fTs[i];
+ double oT = iSpan.fOtherT;
+ Segment* other = iSpan.fOther;
+ int oCount = other->fTs.count();
+ for (int o = 0; o < oCount; ++o) {
+ Span& oSpan = other->fTs[o];
+ if (oT == oSpan.fT && this == oSpan.fOther) {
+ iSpan.fOtherIndex = o;
+ }
+ }
+ }
+ }
+
+ void init(const SkPoint pts[], SkPath::Verb verb) {
+ fPts = pts;
+ fVerb = verb;
+ fDone = false;
+ fCoincident = 0;
+ }
+
bool intersected() const {
return fTs.count() > 0;
}
@@ -916,59 +1058,49 @@
return fBounds.fLeft == fBounds.fRight;
}
- int lastSpan(int end, int step, const SkPoint* startLoc,
- const Span* startSpan, bool& coincident) {
+ int lastSpan(int end, int step, const SkPoint& startLoc,
+ const Span* startSpan, bool& coincident) const {
int last = end;
int count = fTs.count();
- coincident = false;
SkPoint lastLoc;
do {
- if (fTs[last].fCoincident == -step) {
+ end = last;
+ if (fTs[end].fCoincident == -step) {
coincident = true;
}
- if (step > 0 ? ++last < count : --last >= 0) {
- return -1;
+ if (step > 0 ? ++last >= count : --last < 0) {
+ return end;
}
const Span& lastSpan = fTs[last];
if (lastSpan.fDone == -step) {
- return -1;
+ return end;
}
if (lastSpan.fT == startSpan->fT) {
continue;
}
xyAtT(lastSpan.fT, &lastLoc);
- } while (*startLoc == lastLoc);
- return last;
+ } while (startLoc == lastLoc);
+ return end;
}
SkScalar leftMost(int start, int end) const {
return (*SegmentLeftMost[fVerb])(fPts, fTs[start].fT, fTs[end].fT);
}
- int matchSpan(const Segment* match, double matchT)
- {
- int count = fTs.count();
- for (int index = 0; index < count; ++index) {
- Span& span = fTs[index];
- if (span.fOther != match) {
- continue;
- }
- if (span.fOtherT != matchT) {
- continue;
- }
- return index;
- }
- SkASSERT(0); // should never get here
- return -1;
- }
-
int nextSpan(int from, int step, const SkPoint& fromLoc,
- const Span* fromSpan, SkPoint* toLoc, Span** toSpan) {
+ const Span* fromSpan, SkPoint* toLoc, bool& coincident) const {
+ coincident = false;
+ if (fDone) {
+ return -1;
+ }
int count = fTs.count();
int to = from;
while (step > 0 ? ++to < count : --to >= 0) {
Span* span = &fTs[to];
- if (span->fT == fromSpan->fT) {
+ if (span->fCoincident == step) {
+ coincident = true;
+ }
+ if (fromSpan->fT == span->fT) {
continue;
}
SkPoint loc;
@@ -976,12 +1108,12 @@
if (fromLoc == loc) {
continue;
}
+ if (span->fDone == -step) {
+ return -1;
+ }
if (toLoc) {
*toLoc = loc;
}
- if (toSpan) {
- *toSpan = span;
- }
return to;
}
return -1;
@@ -992,32 +1124,36 @@
}
void reset() {
- fPts = NULL;
- fVerb = (SkPath::Verb) -1;
+ init(NULL, (SkPath::Verb) -1);
fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
fTs.reset();
- fDone = false;
- fCoincident = 0;
}
// OPTIMIZATION: remove this function if it's never called
double t(int tIndex) const {
return fTs[tIndex].fT;
}
+
+ void updatePts(const SkPoint pts[]) {
+ fPts = pts;
+ }
SkPath::Verb verb() const {
return fVerb;
}
SkScalar xAtT(double t) const {
+ SkASSERT(t >= 0 && t <= 1);
return (*SegmentXAtT[fVerb])(fPts, t);
}
void xyAtT(double t, SkPoint* pt) const {
+ SkASSERT(t >= 0 && t <= 1);
(*SegmentXYAtT[fVerb])(fPts, t, pt);
}
SkScalar yAtT(double t) const {
+ SkASSERT(t >= 0 && t <= 1);
return (*SegmentYAtT[fVerb])(fPts, t);
}
@@ -1073,13 +1209,15 @@
fContainsCurves = true;
}
- void addLine(const SkPoint pts[2]) {
+ int addLine(const SkPoint pts[2]) {
fSegments.push_back().addLine(pts);
+ return fSegments.count();
}
- void addQuad(const SkPoint pts[3]) {
+ int addQuad(const SkPoint pts[3]) {
fSegments.push_back().addQuad(pts);
fContainsCurves = true;
+ return fSegments.count();
}
const Bounds& bounds() const {
@@ -1102,6 +1240,13 @@
}
}
+ void fixOtherTIndex() {
+ int segmentCount = fSegments.count();
+ for (int sIndex = 0; sIndex < segmentCount; ++sIndex) {
+ fSegments[sIndex].fixOtherTIndex();
+ }
+ }
+
void reset() {
fSegments.reset();
fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
@@ -1217,12 +1362,6 @@
}
}
-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
@@ -1242,12 +1381,17 @@
SkPath::Verb reducedVerb;
uint8_t* verbPtr = fPathVerbs.begin();
const SkPoint* pointsPtr = fPathPts.begin();
+ const SkPoint* finalCurveStart = NULL;
+ const SkPoint* finalCurveEnd = NULL;
while ((verb = (SkPath::Verb) *verbPtr++) != SkPath::kDone_Verb) {
switch (verb) {
case SkPath::kMove_Verb:
complete();
- startContour();
- pointsPtr += 1;
+ if (!fCurrentContour) {
+ fCurrentContour = fContours.push_back_n(1);
+ finalCurveEnd = pointsPtr++;
+ *fExtra.append() = -1; // start new contour
+ }
continue;
case SkPath::kLine_Verb:
// skip degenerate points
@@ -1257,12 +1401,14 @@
}
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);
+ *fExtra.append() =
+ fCurrentContour->addLine(fReducePts.end() - 2);
break;
}
fCurrentContour->addQuad(&pointsPtr[-1]);
@@ -1273,23 +1419,33 @@
break; // skip degenerate points
}
if (reducedVerb == 1) {
- fCurrentContour->addLine(fReducePts.end() - 2);
+ *fExtra.append() =
+ fCurrentContour->addLine(fReducePts.end() - 2);
break;
}
if (reducedVerb == 2) {
- fCurrentContour->addQuad(fReducePts.end() - 3);
+ *fExtra.append() =
+ fCurrentContour->addQuad(fReducePts.end() - 3);
break;
}
fCurrentContour->addCubic(&pointsPtr[-1]);
break;
case SkPath::kClose_Verb:
SkASSERT(fCurrentContour);
+ if (finalCurveStart && finalCurveEnd
+ && *finalCurveStart != *finalCurveEnd) {
+ *fReducePts.append() = *finalCurveStart;
+ *fReducePts.append() = *finalCurveEnd;
+ *fExtra.append() =
+ fCurrentContour->addLine(fReducePts.end() - 2);
+ }
complete();
continue;
default:
SkDEBUGFAIL("bad verb");
return;
}
+ finalCurveStart = &pointsPtr[verb - 1];
pointsPtr += verb;
SkASSERT(fCurrentContour);
}
@@ -1297,6 +1453,24 @@
if (fCurrentContour && !fCurrentContour->fSegments.count()) {
fContours.pop_back();
}
+ // correct pointers in contours since fReducePts may have moved as it grew
+ int cIndex = 0;
+ fCurrentContour = &fContours[0];
+ int extraCount = fExtra.count();
+ SkASSERT(fExtra[0] == -1);
+ int eIndex = 0;
+ int rIndex = 0;
+ while (++eIndex < extraCount) {
+ int offset = fExtra[eIndex];
+ if (offset < 0) {
+ fCurrentContour = &fContours[++cIndex];
+ continue;
+ }
+ Segment& segment = fCurrentContour->fSegments[offset - 1];
+ segment.updatePts(&fReducePts[rIndex]);
+ rIndex += segment.verb() + 1;
+ }
+ fExtra.reset(); // we're done with this
}
private:
@@ -1306,6 +1480,7 @@
Contour* fCurrentContour;
SkTArray<Contour>& fContours;
SkTDArray<SkPoint> fReducePts; // segments created on the fly
+ SkTDArray<int> fExtra; // -1 marks new contour, > 0 offsets into contour
};
class Work {
@@ -1318,8 +1493,10 @@
kCubic_Segment = SkPath::kCubic_Verb,
};
- void addOtherT(int index, double other) {
- fContour->fSegments[fIndex].addOtherT(index, other);
+ // FIXME: does it make sense to write otherIndex now if we're going to
+ // fix it up later?
+ void addOtherT(int index, double otherT, int otherIndex) {
+ fContour->fSegments[fIndex].addOtherT(index, otherT, otherIndex);
}
// Avoid collapsing t values that are close to the same since
@@ -1436,29 +1613,34 @@
const Work& wn, const double wtTs[2], const double wnTs[2]) {
#if DEBUG_ADD_INTERSECTING_TS
if (!pts) {
+ SkDebugf("%s no intersect (%1.9g,%1.9g %1.9g,%1.9g) (%1.9g,%1.9g %1.9g,%1.9g)\n",
+ __FUNCTION__, wt.pts()[0].fX, wt.pts()[0].fY,
+ wt.pts()[1].fX, wt.pts()[1].fY, wn.pts()[0].fX, wn.pts()[0].fY,
+ wn.pts()[1].fX, wn.pts()[1].fY);
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",
+ SkDebugf("%s wtTs[0]=%g (%g,%g, %g,%g) (%g,%g)",
__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(" wtTs[1]=%g", wtTs[1]);
}
- SkDebugf("%s wnTs[0]=%g (%g,%g, %g,%g) (%g,%g)\n",
- __FUNCTION__,
+ SkDebugf(" wnTs[0]=%g (%g,%g, %g,%g) (%g,%g)\n",
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]);
+ SkDebugf(" wnTs[1]=%g", wnTs[1]);
+ SkDebugf("\n");
}
#endif
}
static bool addIntersectTs(Contour* test, Contour* next, int winding) {
+
if (test != next) {
if (test->bounds().fBottom < next->bounds().fTop) {
return false;
@@ -1467,10 +1649,11 @@
return true;
}
}
- Work wt, wn;
+ Work wt;
wt.init(test);
- wn.init(next);
do {
+ Work wn;
+ wn.init(next);
if (test == next && !wn.startAfter(wt)) {
continue;
}
@@ -1490,6 +1673,8 @@
case Work::kLine_Segment: {
pts = HLineIntersect(wn.pts(), wt.left(),
wt.right(), wt.y(), wt.xFlipped(), ts);
+ debugShowLineIntersection(pts, wt, wn,
+ ts.fT[1], ts.fT[0]);
break;
}
case Work::kQuad_Segment: {
@@ -1514,6 +1699,8 @@
case Work::kLine_Segment: {
pts = VLineIntersect(wn.pts(), wt.top(),
wt.bottom(), wt.x(), wt.yFlipped(), ts);
+ debugShowLineIntersection(pts, wt, wn,
+ ts.fT[1], ts.fT[0]);
break;
}
case Work::kQuad_Segment: {
@@ -1535,10 +1722,14 @@
case Work::kHorizontalLine_Segment:
pts = HLineIntersect(wt.pts(), wn.left(),
wn.right(), wn.y(), wn.xFlipped(), ts);
+ debugShowLineIntersection(pts, wt, wn,
+ ts.fT[1], ts.fT[0]);
break;
case Work::kVerticalLine_Segment:
pts = VLineIntersect(wt.pts(), wn.top(),
wn.bottom(), wn.x(), wn.yFlipped(), ts);
+ debugShowLineIntersection(pts, wt, wn,
+ ts.fT[1], ts.fT[0]);
break;
case Work::kLine_Segment: {
pts = LineIntersect(wt.pts(), wn.pts(), ts);
@@ -1625,8 +1816,8 @@
SkASSERT(ts.fT[1][pt] >= 0 && ts.fT[1][pt] <= 1);
int testTAt = wt.addT(ts.fT[swap][pt], wn, coincident);
int nextTAt = wn.addT(ts.fT[!swap][pt], wt, coincident);
- wt.addOtherT(testTAt, ts.fT[!swap][pt]);
- wn.addOtherT(nextTAt, ts.fT[swap][pt]);
+ wt.addOtherT(testTAt, ts.fT[!swap][pt], nextTAt);
+ wn.addOtherT(nextTAt, ts.fT[swap][pt], testTAt);
coincident = -coincident;
}
} while (wn.advance());
@@ -1643,6 +1834,39 @@
}
}
+
+// OPTIMIZATION: not crazy about linear search here to find top active y.
+// seems like we should break down and do the sort, or maybe sort each
+// contours' segments?
+// Once the segment array is built, there's no reason I can think of not to
+// sort it in Y. hmmm
+static Segment* findTopContour(SkTDArray<Contour*>& contourList,
+ int contourCount) {
+ int cIndex = 0;
+ Segment* topStart;
+ do {
+ Contour* topContour = contourList[cIndex];
+ topStart = topContour->topSegment();
+ } while (!topStart && ++cIndex < contourCount);
+ if (!topStart) {
+ return NULL;
+ }
+ SkScalar top = topStart->bounds().fTop;
+ for (int cTest = cIndex + 1; cTest < contourCount; ++cTest) {
+ Contour* contour = contourList[cTest];
+ if (top < contour->bounds().fTop) {
+ continue;
+ }
+ Segment* test = contour->topSegment();
+ if (top > test->bounds().fTop) {
+ cIndex = cTest;
+ topStart = test;
+ top = test->bounds().fTop;
+ }
+ }
+ return topStart;
+}
+
// 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
@@ -1650,41 +1874,26 @@
// '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.
-
+ // since we start with leftmost top edge, we'll traverse through a
+ // smaller angle counterclockwise to get to the next edge.
static void bridge(SkTDArray<Contour*>& contourList) {
int contourCount = contourList.count();
+ int winding = 0; // there are no contours outside this one
do {
- // OPTIMIZATION: not crazy about linear search here to find top active y.
- // seems like we should break down and do the sort, or maybe sort each
- // contours' segments?
- // Once the segment array is built, there's no reason I can think of not to
- // sort it in Y. hmmm
- int cIndex = 0;
- Segment* topStart;
- do {
- Contour* topContour = contourList[cIndex];
- topStart = topContour->topSegment();
- } while (!topStart && ++cIndex < contourCount);
+ Segment* topStart = findTopContour(contourList, contourCount);
if (!topStart) {
break;
}
- SkScalar top = topStart->bounds().fTop;
- for (int cTest = cIndex + 1; cTest < contourCount; ++cTest) {
- Contour* contour = contourList[cTest];
- if (top < contour->bounds().fTop) {
- continue;
- }
- Segment* test = contour->topSegment();
- if (top > test->bounds().fTop) {
- cIndex = cTest;
- topStart = test;
- top = test->bounds().fTop;
- }
- }
- int index;
- const Segment* topSegment = topStart->findTop(index);
// Start at the top. Above the top is outside, below is inside.
- // follow edges to intersection
+ // follow edges to intersection by changing the tIndex by direction.
+ int tIndex, step;
+ const Segment* topSegment = topStart->findTop(tIndex, step);
+ const Segment* next = topSegment;
+ do {
+ int spanIndex;
+ next = next->findNext(tIndex, winding, step, spanIndex);
+ } while (next != topSegment);
+
// at intersection, stay on outside, but mark remaining edges as inside
// or, only mark first pair as inside?
// how is this going to work for contained (but not intersecting)
@@ -1700,6 +1909,14 @@
}
+static void fixOtherTIndex(SkTDArray<Contour*>& contourList) {
+ int contourCount = contourList.count();
+ for (int cTest = 0; cTest < contourCount; ++cTest) {
+ Contour* contour = contourList[cTest];
+ contour->fixOtherTIndex();
+ }
+}
+
static void makeContourList(SkTArray<Contour>& contours, Contour& sentinel,
SkTDArray<Contour*>& list) {
int count = contours.count();
@@ -1740,6 +1957,7 @@
next = *nextPtr++;
} while (next != &sentinel && addIntersectTs(current, next, winding));
} while (*currentPtr != &sentinel);
+ fixOtherTIndex(contourList);
// eat through coincident edges
coincidenceCheck(contourList, winding);
// construct closed contours