| Index: src/pathops/SkPathOpsWinding.cpp
|
| diff --git a/src/pathops/SkPathOpsWinding.cpp b/src/pathops/SkPathOpsWinding.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..53083e484e6e2896cb63ffb91f382cd1ebd35aa4
|
| --- /dev/null
|
| +++ b/src/pathops/SkPathOpsWinding.cpp
|
| @@ -0,0 +1,402 @@
|
| +/*
|
| + * Copyright 2015 Google Inc.
|
| + *
|
| + * Use of this source code is governed by a BSD-style license that can be
|
| + * found in the LICENSE file.
|
| + */
|
| +
|
| +// given a prospective edge, compute its initial winding by projecting a ray
|
| +// if the ray hits another edge
|
| + // if the edge doesn't have a winding yet, hop up to that edge and start over
|
| + // concern : check for hops forming a loop
|
| + // if the edge is unsortable, or
|
| + // the intersection is nearly at the ends, or
|
| + // the tangent at the intersection is nearly coincident to the ray,
|
| + // choose a different ray and try again
|
| + // concern : if it is unable to succeed after N tries, try another edge? direction?
|
| +// if no edge is hit, compute the winding directly
|
| +
|
| +// given the top span, project the most perpendicular ray and look for intersections
|
| + // let's try up and then down. What the hey
|
| +
|
| +// bestXY is initialized by caller with basePt
|
| +
|
| +#include "SkOpContour.h"
|
| +#include "SkOpSegment.h"
|
| +#include "SkPathOpsCurve.h"
|
| +
|
| +enum class SkOpRayDir {
|
| + kLeft,
|
| + kTop,
|
| + kRight,
|
| + kBottom,
|
| +};
|
| +
|
| +#if DEBUG_WINDING
|
| +const char* gDebugRayDirName[] = {
|
| + "kLeft",
|
| + "kTop",
|
| + "kRight",
|
| + "kBottom"
|
| +};
|
| +#endif
|
| +
|
| +static int xy_index(SkOpRayDir dir) {
|
| + return static_cast<int>(dir) & 1;
|
| +}
|
| +
|
| +static SkScalar pt_xy(const SkPoint& pt, SkOpRayDir dir) {
|
| + return (&pt.fX)[xy_index(dir)];
|
| +}
|
| +
|
| +static SkScalar pt_yx(const SkPoint& pt, SkOpRayDir dir) {
|
| + return (&pt.fX)[!xy_index(dir)];
|
| +}
|
| +
|
| +static double pt_dxdy(const SkDVector& v, SkOpRayDir dir) {
|
| + return (&v.fX)[xy_index(dir)];
|
| +}
|
| +
|
| +static double pt_dydx(const SkDVector& v, SkOpRayDir dir) {
|
| + return (&v.fX)[!xy_index(dir)];
|
| +}
|
| +
|
| +static SkScalar rect_side(const SkRect& r, SkOpRayDir dir) {
|
| + return (&r.fLeft)[static_cast<int>(dir)];
|
| +}
|
| +
|
| +static bool sideways_overlap(const SkRect& rect, const SkPoint& pt, SkOpRayDir dir) {
|
| + int i = !xy_index(dir);
|
| + return approximately_between((&rect.fLeft)[i], (&pt.fX)[i], (&rect.fRight)[i]);
|
| +}
|
| +
|
| +static bool less_than(SkOpRayDir dir) {
|
| + return static_cast<bool>((static_cast<int>(dir) & 2) == 0);
|
| +}
|
| +
|
| +static bool ccw_dxdy(const SkDVector& v, SkOpRayDir dir) {
|
| + bool vPartPos = pt_dydx(v, dir) > 0;
|
| + bool leftBottom = ((static_cast<int>(dir) + 1) & 2) != 0;
|
| + return vPartPos == leftBottom;
|
| +}
|
| +
|
| +struct SkOpRayHit {
|
| + SkOpRayDir makeTestBase(SkOpSpan* span, double t) {
|
| + fNext = NULL;
|
| + fSpan = span;
|
| + fT = span->t() * (1 - t) + span->next()->t() * t;
|
| + SkOpSegment* segment = span->segment();
|
| + fSlope = segment->dSlopeAtT(fT);
|
| + fPt = segment->ptAtT(fT);
|
| + fValid = true;
|
| + return fabs(fSlope.fX) < fabs(fSlope.fY) ? SkOpRayDir::kLeft : SkOpRayDir::kTop;
|
| + }
|
| +
|
| + SkOpRayHit* fNext;
|
| + SkOpSpan* fSpan;
|
| + SkPoint fPt;
|
| + double fT;
|
| + SkDVector fSlope;
|
| + bool fValid;
|
| +};
|
| +
|
| +void SkOpContour::rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits,
|
| + SkChunkAlloc* allocator) {
|
| + // if the bounds extreme is outside the best, we're done
|
| + SkScalar baseXY = pt_xy(base.fPt, dir);
|
| + SkScalar boundsXY = rect_side(fBounds, dir);
|
| + bool checkLessThan = less_than(dir);
|
| + if (!approximately_equal(baseXY, boundsXY) && (baseXY < boundsXY) == checkLessThan) {
|
| + return;
|
| + }
|
| + SkOpSegment* testSegment = &fHead;
|
| + do {
|
| + testSegment->rayCheck(base, dir, hits, allocator);
|
| + } while ((testSegment = testSegment->next()));
|
| +}
|
| +
|
| +void SkOpSegment::rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits,
|
| + SkChunkAlloc* allocator) {
|
| + if (!sideways_overlap(fBounds, base.fPt, dir)) {
|
| + return;
|
| + }
|
| + SkScalar baseXY = pt_xy(base.fPt, dir);
|
| + SkScalar boundsXY = rect_side(fBounds, dir);
|
| + bool checkLessThan = less_than(dir);
|
| + if (!approximately_equal(baseXY, boundsXY) && (baseXY < boundsXY) == checkLessThan) {
|
| + return;
|
| + }
|
| + double tVals[3];
|
| + SkScalar baseYX = pt_yx(base.fPt, dir);
|
| + int roots = (*CurveIntercept[fVerb * 2 + xy_index(dir)])(fPts, fWeight, baseYX, tVals);
|
| + for (int index = 0; index < roots; ++index) {
|
| + double t = tVals[index];
|
| + if (base.fSpan->segment() == this && approximately_equal(base.fT, t)) {
|
| + continue;
|
| + }
|
| + SkDVector slope;
|
| + SkPoint pt;
|
| + SkDEBUGCODE(sk_bzero(&slope, sizeof(slope)));
|
| + bool valid = false;
|
| + if (approximately_zero(t)) {
|
| + pt = fPts[0];
|
| + } else if (approximately_equal(t, 1)) {
|
| + pt = fPts[SkPathOpsVerbToPoints(fVerb)];
|
| + } else {
|
| + SkASSERT(between(0, t, 1));
|
| + pt = this->ptAtT(t);
|
| + if (SkDPoint::ApproximatelyEqual(pt, base.fPt)) {
|
| + if (base.fSpan->segment() == this) {
|
| + continue;
|
| + }
|
| + } else {
|
| + SkScalar ptXY = pt_xy(pt, dir);
|
| + if (!approximately_equal(baseXY, ptXY) && (baseXY < ptXY) == checkLessThan) {
|
| + continue;
|
| + }
|
| + slope = this->dSlopeAtT(t);
|
| + if (fVerb == SkPath::kCubic_Verb && base.fSpan->segment() == this
|
| + && roughly_equal(base.fT, t)
|
| + && SkDPoint::RoughlyEqual(pt, base.fPt)) {
|
| + #if DEBUG_WINDING
|
| + SkDebugf("%s (rarely expect this)\n", __FUNCTION__);
|
| + #endif
|
| + continue;
|
| + }
|
| + if (fabs(pt_dydx(slope, dir) * 10000) > fabs(pt_dxdy(slope, dir))) {
|
| + valid = true;
|
| + }
|
| + }
|
| + }
|
| + SkOpSpan* span = this->windingSpanAtT(t);
|
| + if (!span) {
|
| + valid = false;
|
| + } else if (!span->windValue() && !span->oppValue()) {
|
| + continue;
|
| + }
|
| + SkOpRayHit* newHit = SkOpTAllocator<SkOpRayHit>::Allocate(allocator);
|
| + newHit->fNext = *hits;
|
| + newHit->fPt = pt;
|
| + newHit->fSlope = slope;
|
| + newHit->fSpan = span;
|
| + newHit->fT = t;
|
| + newHit->fValid = valid;
|
| + *hits = newHit;
|
| + }
|
| +}
|
| +
|
| +SkOpSpan* SkOpSegment::windingSpanAtT(double tHit) {
|
| + SkOpSpan* span = &fHead;
|
| + SkOpSpanBase* next;
|
| + do {
|
| + next = span->next();
|
| + if (approximately_equal(tHit, next->t())) {
|
| + return NULL;
|
| + }
|
| + if (tHit < next->t()) {
|
| + return span;
|
| + }
|
| + } while (!next->final() && (span = next->upCast()));
|
| + return NULL;
|
| +}
|
| +
|
| +static bool hit_compare_x(const SkOpRayHit* a, const SkOpRayHit* b) {
|
| + return a->fPt.fX < b->fPt.fX;
|
| +}
|
| +
|
| +static bool reverse_hit_compare_x(const SkOpRayHit* a, const SkOpRayHit* b) {
|
| + return b->fPt.fX < a->fPt.fX;
|
| +}
|
| +
|
| +static bool hit_compare_y(const SkOpRayHit* a, const SkOpRayHit* b) {
|
| + return a->fPt.fY < b->fPt.fY;
|
| +}
|
| +
|
| +static bool reverse_hit_compare_y(const SkOpRayHit* a, const SkOpRayHit* b) {
|
| + return b->fPt.fY < a->fPt.fY;
|
| +}
|
| +
|
| +static double get_t_guess(int tTry, int* dirOffset) {
|
| + double t = 0.5;
|
| + *dirOffset = tTry & 1;
|
| + int tBase = tTry >> 1;
|
| + int tBits = 0;
|
| + while (tTry >>= 1) {
|
| + t /= 2;
|
| + ++tBits;
|
| + }
|
| + if (tBits) {
|
| + int tIndex = (tBase - 1) & ((1 << tBits) - 1);
|
| + t += t * 2 * tIndex;
|
| + }
|
| + return t;
|
| +}
|
| +
|
| +bool SkOpSpan::sortableTop(SkOpContour* contourHead) {
|
| + SkChunkAlloc allocator(1024);
|
| + int dirOffset;
|
| + double t = get_t_guess(fTopTTry++, &dirOffset);
|
| + SkOpRayHit hitBase;
|
| + SkOpRayDir dir = hitBase.makeTestBase(this, t);
|
| + if (hitBase.fSlope.fX == 0 && hitBase.fSlope.fY == 0) {
|
| + return false;
|
| + }
|
| + SkOpRayHit* hitHead = &hitBase;
|
| + dir = static_cast<SkOpRayDir>(static_cast<int>(dir) + dirOffset);
|
| + SkOpContour* contour = contourHead;
|
| + do {
|
| + contour->rayCheck(hitBase, dir, &hitHead, &allocator);
|
| + } while ((contour = contour->next()));
|
| + // sort hits
|
| + SkSTArray<1, SkOpRayHit*> sorted;
|
| + SkOpRayHit* hit = hitHead;
|
| + while (hit) {
|
| + sorted.push_back(hit);
|
| + hit = hit->fNext;
|
| + }
|
| + int count = sorted.count();
|
| + SkTQSort(sorted.begin(), sorted.end() - 1, xy_index(dir)
|
| + ? less_than(dir) ? hit_compare_y : reverse_hit_compare_y
|
| + : less_than(dir) ? hit_compare_x : reverse_hit_compare_x);
|
| + // verify windings
|
| +#if DEBUG_WINDING
|
| + SkDebugf("%s dir=%s seg=%d t=%1.9g pt=(%1.9g,%1.9g)\n", __FUNCTION__,
|
| + gDebugRayDirName[static_cast<int>(dir)], hitBase.fSpan->segment()->debugID(),
|
| + hitBase.fT, hitBase.fPt.fX, hitBase.fPt.fY);
|
| + for (int index = 0; index < count; ++index) {
|
| + hit = sorted[index];
|
| + SkOpSpan* span = hit->fSpan;
|
| + SkOpSegment* hitSegment = span ? span->segment() : NULL;
|
| + bool operand = span ? hitSegment->operand() : false;
|
| + bool ccw = ccw_dxdy(hit->fSlope, dir);
|
| + SkDebugf("%s [%d] valid=%d operand=%d span=%d ccw=%d ", __FUNCTION__, index,
|
| + hit->fValid, operand, span ? span->debugID() : -1, ccw);
|
| + if (span) {
|
| + hitSegment->dumpPtsInner();
|
| + }
|
| + SkDebugf(" t=%1.9g pt=(%1.9g,%1.9g) slope=(%1.9g,%1.9g)\n", hit->fT,
|
| + hit->fPt.fX, hit->fPt.fY, hit->fSlope.fX, hit->fSlope.fY);
|
| + }
|
| +#endif
|
| + const SkPoint* last = NULL;
|
| + int wind = 0;
|
| + int oppWind = 0;
|
| + for (int index = 0; index < count; ++index) {
|
| + hit = sorted[index];
|
| + if (!hit->fValid) {
|
| + return false;
|
| + }
|
| + bool ccw = ccw_dxdy(hit->fSlope, dir);
|
| + SkASSERT(!approximately_zero(hit->fT) || !hit->fValid);
|
| + SkOpSpan* span = hit->fSpan;
|
| + SkOpSegment* hitSegment = span->segment();
|
| + if (!span) {
|
| + return false;
|
| + }
|
| + if (span->windValue() == 0 && span->oppValue() == 0) {
|
| + continue;
|
| + }
|
| + if (last && SkDPoint::ApproximatelyEqual(*last, hit->fPt)) {
|
| + return false;
|
| + }
|
| + if (index < count - 1) {
|
| + const SkPoint& next = sorted[index + 1]->fPt;
|
| + if (SkDPoint::ApproximatelyEqual(next, hit->fPt)) {
|
| + return false;
|
| + }
|
| + }
|
| + bool operand = hitSegment->operand();
|
| + if (operand) {
|
| + SkTSwap(wind, oppWind);
|
| + }
|
| + int lastWind = wind;
|
| + int lastOpp = oppWind;
|
| + int windValue = ccw ? -span->windValue() : span->windValue();
|
| + int oppValue = ccw ? -span->oppValue() : span->oppValue();
|
| + wind += windValue;
|
| + oppWind += oppValue;
|
| + bool sumSet = false;
|
| + int spanSum = span->windSum();
|
| + int windSum = SkOpSegment::UseInnerWinding(lastWind, wind) ? wind : lastWind;
|
| + if (spanSum == SK_MinS32) {
|
| + span->setWindSum(windSum);
|
| + sumSet = true;
|
| + } else {
|
| + // the need for this condition suggests that UseInnerWinding is flawed
|
| + // happened when last = 1 wind = -1
|
| +#if 0
|
| + SkASSERT((hitSegment->isXor() ? (windSum & 1) == (spanSum & 1) : windSum == spanSum)
|
| + || (abs(wind) == abs(lastWind)
|
| + && (windSum ^ wind ^ lastWind) == spanSum));
|
| +#endif
|
| + }
|
| + int oSpanSum = span->oppSum();
|
| + int oppSum = SkOpSegment::UseInnerWinding(lastOpp, oppWind) ? oppWind : lastOpp;
|
| + if (oSpanSum == SK_MinS32) {
|
| + span->setOppSum(oppSum);
|
| + } else {
|
| +#if 0
|
| + SkASSERT(hitSegment->oppXor() ? (oppSum & 1) == (oSpanSum & 1) : oppSum == oSpanSum
|
| + || (abs(oppWind) == abs(lastOpp)
|
| + && (oppSum ^ oppWind ^ lastOpp) == oSpanSum));
|
| +#endif
|
| + }
|
| + if (sumSet) {
|
| + (void) hitSegment->markAndChaseWinding(span, span->next(), windSum, oppSum, NULL);
|
| + (void) hitSegment->markAndChaseWinding(span->next(), span, windSum, oppSum, NULL);
|
| + }
|
| + if (operand) {
|
| + SkTSwap(wind, oppWind);
|
| + }
|
| + last = &hit->fPt;
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +SkOpSpan* SkOpSegment::findSortableTop(SkOpContour* contourHead) {
|
| + SkOpSpan* span = &fHead;
|
| + SkOpSpanBase* next;
|
| + do {
|
| + next = span->next();
|
| + if (span->done()) {
|
| + continue;
|
| + }
|
| + if (span->windSum() != SK_MinS32) {
|
| + return span;
|
| + }
|
| + if (span->sortableTop(contourHead)) {
|
| + return span;
|
| + }
|
| + } while (!next->final() && (span = next->upCast()));
|
| + return NULL;
|
| +}
|
| +
|
| +SkOpSpan* SkOpContour::findSortableTop(SkOpContour* contourHead) {
|
| + SkOpSegment* testSegment = &fHead;
|
| + do {
|
| + if (testSegment->done()) {
|
| + continue;
|
| + }
|
| + SkOpSpan* result = testSegment->findSortableTop(contourHead);
|
| + if (result) {
|
| + return result;
|
| + }
|
| + } while ((testSegment = testSegment->next()));
|
| + return NULL;
|
| +}
|
| +
|
| +SkOpSpan* FindSortableTop(SkOpContourHead* contourHead) {
|
| + for (int index = 0; index < SkOpGlobalState::kMaxWindingTries; ++index) {
|
| + SkOpContour* contour = contourHead;
|
| + do {
|
| + if (contour->done()) {
|
| + continue;
|
| + }
|
| + SkOpSpan* result = contour->findSortableTop(contourHead);
|
| + if (result) {
|
| + return result;
|
| + }
|
| + } while ((contour = contour->next()));
|
| + }
|
| + return NULL;
|
| +}
|
|
|