| /* | |
| * Copyright 2014 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "src/pathops/SkPathOpsTSect.h" | |
| #include "include/private/SkFloatingPoint.h" | |
| #include "include/private/SkMacros.h" | |
| #include "include/private/SkTArray.h" | |
| #include "src/core/SkTSort.h" | |
| #include "src/pathops/SkIntersections.h" | |
| #include "src/pathops/SkPathOpsConic.h" | |
| #include "src/pathops/SkPathOpsCubic.h" | |
| #include "src/pathops/SkPathOpsLine.h" | |
| #include "src/pathops/SkPathOpsQuad.h" | |
| #include <cfloat> | |
| #include <algorithm> | |
| #include <array> | |
| #include <cmath> | |
| using namespace skia_private; | |
| #define COINCIDENT_SPAN_COUNT 9 | |
| void SkTCoincident::setPerp(const SkTCurve& c1, double t, | |
| const SkDPoint& cPt, const SkTCurve& c2) { | |
| SkDVector dxdy = c1.dxdyAtT(t); | |
| SkDLine perp = {{ cPt, {cPt.fX + dxdy.fY, cPt.fY - dxdy.fX} }}; | |
| SkIntersections i SkDEBUGCODE((c1.globalState())); | |
| int used = i.intersectRay(c2, perp); | |
| // only keep closest | |
| if (used == 0 || used == 3) { | |
| this->init(); | |
| return; | |
| } | |
| fPerpT = i[0][0]; | |
| fPerpPt = i.pt(0); | |
| SkASSERT(used <= 2); | |
| if (used == 2) { | |
| double distSq = (fPerpPt - cPt).lengthSquared(); | |
| double dist2Sq = (i.pt(1) - cPt).lengthSquared(); | |
| if (dist2Sq < distSq) { | |
| fPerpT = i[0][1]; | |
| fPerpPt = i.pt(1); | |
| } | |
| } | |
| #if DEBUG_T_SECT | |
| SkDebugf("setPerp t=%1.9g cPt=(%1.9g,%1.9g) %s oppT=%1.9g fPerpPt=(%1.9g,%1.9g)\n", | |
| t, cPt.fX, cPt.fY, | |
| cPt.approximatelyEqual(fPerpPt) ? "==" : "!=", fPerpT, fPerpPt.fX, fPerpPt.fY); | |
| #endif | |
| fMatch = cPt.approximatelyEqual(fPerpPt); | |
| #if DEBUG_T_SECT | |
| if (fMatch) { | |
| SkDebugf("%s", ""); // allow setting breakpoint | |
| } | |
| #endif | |
| } | |
| void SkTSpan::addBounded(SkTSpan* span, SkArenaAlloc* heap) { | |
| SkTSpanBounded* bounded = heap->make<SkTSpanBounded>(); | |
| bounded->fBounded = span; | |
| bounded->fNext = fBounded; | |
| fBounded = bounded; | |
| } | |
| SkTSpan* SkTSect::addFollowing( | |
| SkTSpan* prior) { | |
| SkTSpan* result = this->addOne(); | |
| SkDEBUGCODE(result->debugSetGlobalState(this->globalState())); | |
| result->fStartT = prior ? prior->fEndT : 0; | |
| SkTSpan* next = prior ? prior->fNext : fHead; | |
| result->fEndT = next ? next->fStartT : 1; | |
| result->fPrev = prior; | |
| result->fNext = next; | |
| if (prior) { | |
| prior->fNext = result; | |
| } else { | |
| fHead = result; | |
| } | |
| if (next) { | |
| next->fPrev = result; | |
| } | |
| result->resetBounds(fCurve); | |
| // world may not be consistent to call validate here | |
| result->validate(); | |
| return result; | |
| } | |
| void SkTSect::addForPerp(SkTSpan* span, double t) { | |
| if (!span->hasOppT(t)) { | |
| SkTSpan* priorSpan; | |
| SkTSpan* opp = this->spanAtT(t, &priorSpan); | |
| if (!opp) { | |
| opp = this->addFollowing(priorSpan); | |
| #if DEBUG_PERP | |
| SkDebugf("%s priorSpan=%d t=%1.9g opp=%d\n", __FUNCTION__, priorSpan ? | |
| priorSpan->debugID() : -1, t, opp->debugID()); | |
| #endif | |
| } | |
| #if DEBUG_PERP | |
| opp->dump(); SkDebugf("\n"); | |
| SkDebugf("%s addBounded span=%d opp=%d\n", __FUNCTION__, priorSpan ? | |
| priorSpan->debugID() : -1, opp->debugID()); | |
| #endif | |
| opp->addBounded(span, &fHeap); | |
| span->addBounded(opp, &fHeap); | |
| } | |
| this->validate(); | |
| #if DEBUG_T_SECT | |
| span->validatePerpT(t); | |
| #endif | |
| } | |
| double SkTSpan::closestBoundedT(const SkDPoint& pt) const { | |
| double result = -1; | |
| double closest = DBL_MAX; | |
| const SkTSpanBounded* testBounded = fBounded; | |
| while (testBounded) { | |
| const SkTSpan* test = testBounded->fBounded; | |
| double startDist = test->pointFirst().distanceSquared(pt); | |
| if (closest > startDist) { | |
| closest = startDist; | |
| result = test->fStartT; | |
| } | |
| double endDist = test->pointLast().distanceSquared(pt); | |
| if (closest > endDist) { | |
| closest = endDist; | |
| result = test->fEndT; | |
| } | |
| testBounded = testBounded->fNext; | |
| } | |
| SkASSERT(between(0, result, 1)); | |
| return result; | |
| } | |
| #ifdef SK_DEBUG | |
| bool SkTSpan::debugIsBefore(const SkTSpan* span) const { | |
| const SkTSpan* work = this; | |
| do { | |
| if (span == work) { | |
| return true; | |
| } | |
| } while ((work = work->fNext)); | |
| return false; | |
| } | |
| #endif | |
| bool SkTSpan::contains(double t) const { | |
| const SkTSpan* work = this; | |
| do { | |
| if (between(work->fStartT, t, work->fEndT)) { | |
| return true; | |
| } | |
| } while ((work = work->fNext)); | |
| return false; | |
| } | |
| const SkTSect* SkTSpan::debugOpp() const { | |
| return SkDEBUGRELEASE(fDebugSect->debugOpp(), nullptr); | |
| } | |
| SkTSpan* SkTSpan::findOppSpan( | |
| const SkTSpan* opp) const { | |
| SkTSpanBounded* bounded = fBounded; | |
| while (bounded) { | |
| SkTSpan* test = bounded->fBounded; | |
| if (opp == test) { | |
| return test; | |
| } | |
| bounded = bounded->fNext; | |
| } | |
| return nullptr; | |
| } | |
| // returns 0 if no hull intersection | |
| // 1 if hulls intersect | |
| // 2 if hulls only share a common endpoint | |
| // -1 if linear and further checking is required | |
| int SkTSpan::hullCheck(const SkTSpan* opp, | |
| bool* start, bool* oppStart) { | |
| if (fIsLinear) { | |
| return -1; | |
| } | |
| bool ptsInCommon; | |
| if (onlyEndPointsInCommon(opp, start, oppStart, &ptsInCommon)) { | |
| SkASSERT(ptsInCommon); | |
| return 2; | |
| } | |
| bool linear; | |
| if (fPart->hullIntersects(*opp->fPart, &linear)) { | |
| if (!linear) { // check set true if linear | |
| return 1; | |
| } | |
| fIsLinear = true; | |
| fIsLine = fPart->controlsInside(); | |
| return ptsInCommon ? 1 : -1; | |
| } | |
| // hull is not linear; check set true if intersected at the end points | |
| return ((int) ptsInCommon) << 1; // 0 or 2 | |
| } | |
| // OPTIMIZE ? If at_most_end_pts_in_common detects that one quad is near linear, | |
| // use line intersection to guess a better split than 0.5 | |
| // OPTIMIZE Once at_most_end_pts_in_common detects linear, mark span so all future splits are linear | |
| int SkTSpan::hullsIntersect(SkTSpan* opp, | |
| bool* start, bool* oppStart) { | |
| if (!fBounds.intersects(opp->fBounds)) { | |
| return 0; | |
| } | |
| int hullSect = this->hullCheck(opp, start, oppStart); | |
| if (hullSect >= 0) { | |
| return hullSect; | |
| } | |
| hullSect = opp->hullCheck(this, oppStart, start); | |
| if (hullSect >= 0) { | |
| return hullSect; | |
| } | |
| return -1; | |
| } | |
| void SkTSpan::init(const SkTCurve& c) { | |
| fPrev = fNext = nullptr; | |
| fStartT = 0; | |
| fEndT = 1; | |
| fBounded = nullptr; | |
| resetBounds(c); | |
| } | |
| bool SkTSpan::initBounds(const SkTCurve& c) { | |
| if (SkIsNaN(fStartT) || SkIsNaN(fEndT)) { | |
| return false; | |
| } | |
| c.subDivide(fStartT, fEndT, fPart); | |
| fBounds.setBounds(*fPart); | |
| fCoinStart.init(); | |
| fCoinEnd.init(); | |
| fBoundsMax = std::max(fBounds.width(), fBounds.height()); | |
| fCollapsed = fPart->collapsed(); | |
| fHasPerp = false; | |
| fDeleted = false; | |
| #if DEBUG_T_SECT | |
| if (fCollapsed) { | |
| SkDebugf("%s", ""); // for convenient breakpoints | |
| } | |
| #endif | |
| return fBounds.valid(); | |
| } | |
| bool SkTSpan::linearsIntersect(SkTSpan* span) { | |
| int result = this->linearIntersects(*span->fPart); | |
| if (result <= 1) { | |
| return SkToBool(result); | |
| } | |
| SkASSERT(span->fIsLinear); | |
| result = span->linearIntersects(*fPart); | |
| // SkASSERT(result <= 1); | |
| return SkToBool(result); | |
| } | |
| double SkTSpan::linearT(const SkDPoint& pt) const { | |
| SkDVector len = this->pointLast() - this->pointFirst(); | |
| return fabs(len.fX) > fabs(len.fY) | |
| ? (pt.fX - this->pointFirst().fX) / len.fX | |
| : (pt.fY - this->pointFirst().fY) / len.fY; | |
| } | |
| int SkTSpan::linearIntersects(const SkTCurve& q2) const { | |
| // looks like q1 is near-linear | |
| int start = 0, end = fPart->pointLast(); // the outside points are usually the extremes | |
| if (!fPart->controlsInside()) { | |
| double dist = 0; // if there's any question, compute distance to find best outsiders | |
| for (int outer = 0; outer < this->pointCount() - 1; ++outer) { | |
| for (int inner = outer + 1; inner < this->pointCount(); ++inner) { | |
| double test = ((*fPart)[outer] - (*fPart)[inner]).lengthSquared(); | |
| if (dist > test) { | |
| continue; | |
| } | |
| dist = test; | |
| start = outer; | |
| end = inner; | |
| } | |
| } | |
| } | |
| // see if q2 is on one side of the line formed by the extreme points | |
| double origX = (*fPart)[start].fX; | |
| double origY = (*fPart)[start].fY; | |
| double adj = (*fPart)[end].fX - origX; | |
| double opp = (*fPart)[end].fY - origY; | |
| double maxPart = std::max(fabs(adj), fabs(opp)); | |
| double sign = 0; // initialization to shut up warning in release build | |
| for (int n = 0; n < q2.pointCount(); ++n) { | |
| double dx = q2[n].fY - origY; | |
| double dy = q2[n].fX - origX; | |
| double maxVal = std::max(maxPart, std::max(fabs(dx), fabs(dy))); | |
| double test = (q2[n].fY - origY) * adj - (q2[n].fX - origX) * opp; | |
| if (precisely_zero_when_compared_to(test, maxVal)) { | |
| return 1; | |
| } | |
| if (approximately_zero_when_compared_to(test, maxVal)) { | |
| return 3; | |
| } | |
| if (n == 0) { | |
| sign = test; | |
| continue; | |
| } | |
| if (test * sign < 0) { | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| bool SkTSpan::onlyEndPointsInCommon(const SkTSpan* opp, | |
| bool* start, bool* oppStart, bool* ptsInCommon) { | |
| if (opp->pointFirst() == this->pointFirst()) { | |
| *start = *oppStart = true; | |
| } else if (opp->pointFirst() == this->pointLast()) { | |
| *start = false; | |
| *oppStart = true; | |
| } else if (opp->pointLast() == this->pointFirst()) { | |
| *start = true; | |
| *oppStart = false; | |
| } else if (opp->pointLast() == this->pointLast()) { | |
| *start = *oppStart = false; | |
| } else { | |
| *ptsInCommon = false; | |
| return false; | |
| } | |
| *ptsInCommon = true; | |
| const SkDPoint* otherPts[4], * oppOtherPts[4]; | |
| // const SkDPoint* otherPts[this->pointCount() - 1], * oppOtherPts[opp->pointCount() - 1]; | |
| int baseIndex = *start ? 0 : fPart->pointLast(); | |
| fPart->otherPts(baseIndex, otherPts); | |
| opp->fPart->otherPts(*oppStart ? 0 : opp->fPart->pointLast(), oppOtherPts); | |
| const SkDPoint& base = (*fPart)[baseIndex]; | |
| for (int o1 = 0; o1 < this->pointCount() - 1; ++o1) { | |
| SkDVector v1 = *otherPts[o1] - base; | |
| for (int o2 = 0; o2 < opp->pointCount() - 1; ++o2) { | |
| SkDVector v2 = *oppOtherPts[o2] - base; | |
| if (v2.dot(v1) >= 0) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| SkTSpan* SkTSpan::oppT(double t) const { | |
| SkTSpanBounded* bounded = fBounded; | |
| while (bounded) { | |
| SkTSpan* test = bounded->fBounded; | |
| if (between(test->fStartT, t, test->fEndT)) { | |
| return test; | |
| } | |
| bounded = bounded->fNext; | |
| } | |
| return nullptr; | |
| } | |
| bool SkTSpan::removeAllBounded() { | |
| bool deleteSpan = false; | |
| SkTSpanBounded* bounded = fBounded; | |
| while (bounded) { | |
| SkTSpan* opp = bounded->fBounded; | |
| deleteSpan |= opp->removeBounded(this); | |
| bounded = bounded->fNext; | |
| } | |
| return deleteSpan; | |
| } | |
| bool SkTSpan::removeBounded(const SkTSpan* opp) { | |
| if (fHasPerp) { | |
| bool foundStart = false; | |
| bool foundEnd = false; | |
| SkTSpanBounded* bounded = fBounded; | |
| while (bounded) { | |
| SkTSpan* test = bounded->fBounded; | |
| if (opp != test) { | |
| foundStart |= between(test->fStartT, fCoinStart.perpT(), test->fEndT); | |
| foundEnd |= between(test->fStartT, fCoinEnd.perpT(), test->fEndT); | |
| } | |
| bounded = bounded->fNext; | |
| } | |
| if (!foundStart || !foundEnd) { | |
| fHasPerp = false; | |
| fCoinStart.init(); | |
| fCoinEnd.init(); | |
| } | |
| } | |
| SkTSpanBounded* bounded = fBounded; | |
| SkTSpanBounded* prev = nullptr; | |
| while (bounded) { | |
| SkTSpanBounded* boundedNext = bounded->fNext; | |
| if (opp == bounded->fBounded) { | |
| if (prev) { | |
| prev->fNext = boundedNext; | |
| return false; | |
| } else { | |
| fBounded = boundedNext; | |
| return fBounded == nullptr; | |
| } | |
| } | |
| prev = bounded; | |
| bounded = boundedNext; | |
| } | |
| SkOPASSERT(0); | |
| return false; | |
| } | |
| bool SkTSpan::splitAt(SkTSpan* work, double t, SkArenaAlloc* heap) { | |
| fStartT = t; | |
| fEndT = work->fEndT; | |
| if (fStartT == fEndT) { | |
| fCollapsed = true; | |
| return false; | |
| } | |
| work->fEndT = t; | |
| if (work->fStartT == work->fEndT) { | |
| work->fCollapsed = true; | |
| return false; | |
| } | |
| fPrev = work; | |
| fNext = work->fNext; | |
| fIsLinear = work->fIsLinear; | |
| fIsLine = work->fIsLine; | |
| work->fNext = this; | |
| if (fNext) { | |
| fNext->fPrev = this; | |
| } | |
| this->validate(); | |
| SkTSpanBounded* bounded = work->fBounded; | |
| fBounded = nullptr; | |
| while (bounded) { | |
| this->addBounded(bounded->fBounded, heap); | |
| bounded = bounded->fNext; | |
| } | |
| bounded = fBounded; | |
| while (bounded) { | |
| bounded->fBounded->addBounded(this, heap); | |
| bounded = bounded->fNext; | |
| } | |
| return true; | |
| } | |
| void SkTSpan::validate() const { | |
| #if DEBUG_VALIDATE | |
| SkASSERT(this != fPrev); | |
| SkASSERT(this != fNext); | |
| SkASSERT(fNext == nullptr || fNext != fPrev); | |
| SkASSERT(fNext == nullptr || this == fNext->fPrev); | |
| SkASSERT(fPrev == nullptr || this == fPrev->fNext); | |
| this->validateBounded(); | |
| #endif | |
| #if DEBUG_T_SECT | |
| SkASSERT(fBounds.width() || fBounds.height() || fCollapsed); | |
| SkASSERT(fBoundsMax == std::max(fBounds.width(), fBounds.height()) || fCollapsed == 0xFF); | |
| SkASSERT(0 <= fStartT); | |
| SkASSERT(fEndT <= 1); | |
| SkASSERT(fStartT <= fEndT); | |
| SkASSERT(fBounded || fCollapsed == 0xFF); | |
| if (fHasPerp) { | |
| if (fCoinStart.isMatch()) { | |
| validatePerpT(fCoinStart.perpT()); | |
| validatePerpPt(fCoinStart.perpT(), fCoinStart.perpPt()); | |
| } | |
| if (fCoinEnd.isMatch()) { | |
| validatePerpT(fCoinEnd.perpT()); | |
| validatePerpPt(fCoinEnd.perpT(), fCoinEnd.perpPt()); | |
| } | |
| } | |
| #endif | |
| } | |
| void SkTSpan::validateBounded() const { | |
| #if DEBUG_VALIDATE | |
| const SkTSpanBounded* testBounded = fBounded; | |
| while (testBounded) { | |
| SkDEBUGCODE(const SkTSpan* overlap = testBounded->fBounded); | |
| SkASSERT(!overlap->fDeleted); | |
| #if DEBUG_T_SECT | |
| SkASSERT(((this->debugID() ^ overlap->debugID()) & 1) == 1); | |
| SkASSERT(overlap->findOppSpan(this)); | |
| #endif | |
| testBounded = testBounded->fNext; | |
| } | |
| #endif | |
| } | |
| void SkTSpan::validatePerpT(double oppT) const { | |
| const SkTSpanBounded* testBounded = fBounded; | |
| while (testBounded) { | |
| const SkTSpan* overlap = testBounded->fBounded; | |
| if (precisely_between(overlap->fStartT, oppT, overlap->fEndT)) { | |
| return; | |
| } | |
| testBounded = testBounded->fNext; | |
| } | |
| SkASSERT(0); | |
| } | |
| void SkTSpan::validatePerpPt(double t, const SkDPoint& pt) const { | |
| SkASSERT(fDebugSect->fOppSect->fCurve.ptAtT(t) == pt); | |
| } | |
| SkTSect::SkTSect(const SkTCurve& c | |
| SkDEBUGPARAMS(SkOpGlobalState* debugGlobalState) | |
| PATH_OPS_DEBUG_T_SECT_PARAMS(int id)) | |
| : fCurve(c) | |
| , fHeap(sizeof(SkTSpan) * 4) | |
| , fCoincident(nullptr) | |
| , fDeleted(nullptr) | |
| , fActiveCount(0) | |
| , fHung(false) | |
| SkDEBUGPARAMS(fDebugGlobalState(debugGlobalState)) | |
| PATH_OPS_DEBUG_T_SECT_PARAMS(fID(id)) | |
| PATH_OPS_DEBUG_T_SECT_PARAMS(fDebugCount(0)) | |
| PATH_OPS_DEBUG_T_SECT_PARAMS(fDebugAllocatedCount(0)) | |
| { | |
| this->resetRemovedEnds(); | |
| fHead = this->addOne(); | |
| SkDEBUGCODE(fHead->debugSetGlobalState(debugGlobalState)); | |
| fHead->init(c); | |
| } | |
| SkTSpan* SkTSect::addOne() { | |
| SkTSpan* result; | |
| if (fDeleted) { | |
| result = fDeleted; | |
| fDeleted = result->fNext; | |
| } else { | |
| result = fHeap.make<SkTSpan>(fCurve, fHeap); | |
| #if DEBUG_T_SECT | |
| ++fDebugAllocatedCount; | |
| #endif | |
| } | |
| result->reset(); | |
| result->fHasPerp = false; | |
| result->fDeleted = false; | |
| ++fActiveCount; | |
| PATH_OPS_DEBUG_T_SECT_CODE(result->fID = fDebugCount++ * 2 + fID); | |
| SkDEBUGCODE(result->fDebugSect = this); | |
| #ifdef SK_DEBUG | |
| result->debugInit(fCurve, fHeap); | |
| result->fCoinStart.debugInit(); | |
| result->fCoinEnd.debugInit(); | |
| result->fPrev = result->fNext = nullptr; | |
| result->fBounds.debugInit(); | |
| result->fStartT = result->fEndT = result->fBoundsMax = SK_ScalarNaN; | |
| result->fCollapsed = result->fIsLinear = result->fIsLine = 0xFF; | |
| #endif | |
| return result; | |
| } | |
| bool SkTSect::binarySearchCoin(SkTSect* sect2, double tStart, | |
| double tStep, double* resultT, double* oppT, SkTSpan** oppFirst) { | |
| SkTSpan work(fCurve, fHeap); | |
| double result = work.fStartT = work.fEndT = tStart; | |
| SkDEBUGCODE(work.fDebugSect = this); | |
| SkDPoint last = fCurve.ptAtT(tStart); | |
| SkDPoint oppPt; | |
| bool flip = false; | |
| bool contained = false; | |
| bool down = tStep < 0; | |
| const SkTCurve& opp = sect2->fCurve; | |
| do { | |
| tStep *= 0.5; | |
| work.fStartT += tStep; | |
| if (flip) { | |
| tStep = -tStep; | |
| flip = false; | |
| } | |
| work.initBounds(fCurve); | |
| if (work.fCollapsed) { | |
| return false; | |
| } | |
| if (last.approximatelyEqual(work.pointFirst())) { | |
| break; | |
| } | |
| last = work.pointFirst(); | |
| work.fCoinStart.setPerp(fCurve, work.fStartT, last, opp); | |
| if (work.fCoinStart.isMatch()) { | |
| #if DEBUG_T_SECT | |
| work.validatePerpPt(work.fCoinStart.perpT(), work.fCoinStart.perpPt()); | |
| #endif | |
| double oppTTest = work.fCoinStart.perpT(); | |
| if (sect2->fHead->contains(oppTTest)) { | |
| *oppT = oppTTest; | |
| oppPt = work.fCoinStart.perpPt(); | |
| contained = true; | |
| if (down ? result <= work.fStartT : result >= work.fStartT) { | |
| *oppFirst = nullptr; // signal caller to fail | |
| return false; | |
| } | |
| result = work.fStartT; | |
| continue; | |
| } | |
| } | |
| tStep = -tStep; | |
| flip = true; | |
| } while (true); | |
| if (!contained) { | |
| return false; | |
| } | |
| if (last.approximatelyEqual(fCurve[0])) { | |
| result = 0; | |
| } else if (last.approximatelyEqual(this->pointLast())) { | |
| result = 1; | |
| } | |
| if (oppPt.approximatelyEqual(opp[0])) { | |
| *oppT = 0; | |
| } else if (oppPt.approximatelyEqual(sect2->pointLast())) { | |
| *oppT = 1; | |
| } | |
| *resultT = result; | |
| return true; | |
| } | |
| // OPTIMIZE ? keep a sorted list of sizes in the form of a doubly-linked list in quad span | |
| // so that each quad sect has a pointer to the largest, and can update it as spans | |
| // are split | |
| SkTSpan* SkTSect::boundsMax() { | |
| SkTSpan* test = fHead; | |
| SkTSpan* largest = fHead; | |
| bool lCollapsed = largest->fCollapsed; | |
| int safetyNet = 10000; | |
| while ((test = test->fNext)) { | |
| if (!--safetyNet) { | |
| fHung = true; | |
| return nullptr; | |
| } | |
| bool tCollapsed = test->fCollapsed; | |
| if ((lCollapsed && !tCollapsed) || (lCollapsed == tCollapsed && | |
| largest->fBoundsMax < test->fBoundsMax)) { | |
| largest = test; | |
| lCollapsed = test->fCollapsed; | |
| } | |
| } | |
| return largest; | |
| } | |
| bool SkTSect::coincidentCheck(SkTSect* sect2) { | |
| SkTSpan* first = fHead; | |
| if (!first) { | |
| return false; | |
| } | |
| SkTSpan* last, * next; | |
| do { | |
| int consecutive = this->countConsecutiveSpans(first, &last); | |
| next = last->fNext; | |
| if (consecutive < COINCIDENT_SPAN_COUNT) { | |
| continue; | |
| } | |
| this->validate(); | |
| sect2->validate(); | |
| this->computePerpendiculars(sect2, first, last); | |
| this->validate(); | |
| sect2->validate(); | |
| // check to see if a range of points are on the curve | |
| SkTSpan* coinStart = first; | |
| do { | |
| bool success = this->extractCoincident(sect2, coinStart, last, &coinStart); | |
| if (!success) { | |
| return false; | |
| } | |
| } while (coinStart && !last->fDeleted); | |
| if (!fHead || !sect2->fHead) { | |
| break; | |
| } | |
| if (!next || next->fDeleted) { | |
| break; | |
| } | |
| } while ((first = next)); | |
| return true; | |
| } | |
| void SkTSect::coincidentForce(SkTSect* sect2, | |
| double start1s, double start1e) { | |
| SkTSpan* first = fHead; | |
| SkTSpan* last = this->tail(); | |
| SkTSpan* oppFirst = sect2->fHead; | |
| SkTSpan* oppLast = sect2->tail(); | |
| if (!last || !oppLast) { | |
| return; | |
| } | |
| bool deleteEmptySpans = this->updateBounded(first, last, oppFirst); | |
| deleteEmptySpans |= sect2->updateBounded(oppFirst, oppLast, first); | |
| this->removeSpanRange(first, last); | |
| sect2->removeSpanRange(oppFirst, oppLast); | |
| first->fStartT = start1s; | |
| first->fEndT = start1e; | |
| first->resetBounds(fCurve); | |
| first->fCoinStart.setPerp(fCurve, start1s, fCurve[0], sect2->fCurve); | |
| first->fCoinEnd.setPerp(fCurve, start1e, this->pointLast(), sect2->fCurve); | |
| bool oppMatched = first->fCoinStart.perpT() < first->fCoinEnd.perpT(); | |
| double oppStartT = first->fCoinStart.perpT() == -1 ? 0 : std::max(0., first->fCoinStart.perpT()); | |
| double oppEndT = first->fCoinEnd.perpT() == -1 ? 1 : std::min(1., first->fCoinEnd.perpT()); | |
| if (!oppMatched) { | |
| using std::swap; | |
| swap(oppStartT, oppEndT); | |
| } | |
| oppFirst->fStartT = oppStartT; | |
| oppFirst->fEndT = oppEndT; | |
| oppFirst->resetBounds(sect2->fCurve); | |
| this->removeCoincident(first, false); | |
| sect2->removeCoincident(oppFirst, true); | |
| if (deleteEmptySpans) { | |
| this->deleteEmptySpans(); | |
| sect2->deleteEmptySpans(); | |
| } | |
| } | |
| bool SkTSect::coincidentHasT(double t) { | |
| SkTSpan* test = fCoincident; | |
| while (test) { | |
| if (between(test->fStartT, t, test->fEndT)) { | |
| return true; | |
| } | |
| test = test->fNext; | |
| } | |
| return false; | |
| } | |
| int SkTSect::collapsed() const { | |
| int result = 0; | |
| const SkTSpan* test = fHead; | |
| while (test) { | |
| if (test->fCollapsed) { | |
| ++result; | |
| } | |
| test = test->next(); | |
| } | |
| return result; | |
| } | |
| void SkTSect::computePerpendiculars(SkTSect* sect2, | |
| SkTSpan* first, SkTSpan* last) { | |
| if (!last) { | |
| return; | |
| } | |
| const SkTCurve& opp = sect2->fCurve; | |
| SkTSpan* work = first; | |
| SkTSpan* prior = nullptr; | |
| do { | |
| if (!work->fHasPerp && !work->fCollapsed) { | |
| if (prior) { | |
| work->fCoinStart = prior->fCoinEnd; | |
| } else { | |
| work->fCoinStart.setPerp(fCurve, work->fStartT, work->pointFirst(), opp); | |
| } | |
| if (work->fCoinStart.isMatch()) { | |
| double perpT = work->fCoinStart.perpT(); | |
| if (sect2->coincidentHasT(perpT)) { | |
| work->fCoinStart.init(); | |
| } else { | |
| sect2->addForPerp(work, perpT); | |
| } | |
| } | |
| work->fCoinEnd.setPerp(fCurve, work->fEndT, work->pointLast(), opp); | |
| if (work->fCoinEnd.isMatch()) { | |
| double perpT = work->fCoinEnd.perpT(); | |
| if (sect2->coincidentHasT(perpT)) { | |
| work->fCoinEnd.init(); | |
| } else { | |
| sect2->addForPerp(work, perpT); | |
| } | |
| } | |
| work->fHasPerp = true; | |
| } | |
| if (work == last) { | |
| break; | |
| } | |
| prior = work; | |
| work = work->fNext; | |
| SkASSERT(work); | |
| } while (true); | |
| } | |
| int SkTSect::countConsecutiveSpans(SkTSpan* first, | |
| SkTSpan** lastPtr) const { | |
| int consecutive = 1; | |
| SkTSpan* last = first; | |
| do { | |
| SkTSpan* next = last->fNext; | |
| if (!next) { | |
| break; | |
| } | |
| if (next->fStartT > last->fEndT) { | |
| break; | |
| } | |
| ++consecutive; | |
| last = next; | |
| } while (true); | |
| *lastPtr = last; | |
| return consecutive; | |
| } | |
| bool SkTSect::hasBounded(const SkTSpan* span) const { | |
| const SkTSpan* test = fHead; | |
| if (!test) { | |
| return false; | |
| } | |
| do { | |
| if (test->findOppSpan(span)) { | |
| return true; | |
| } | |
| } while ((test = test->next())); | |
| return false; | |
| } | |
| bool SkTSect::deleteEmptySpans() { | |
| SkTSpan* test; | |
| SkTSpan* next = fHead; | |
| int safetyHatch = 1000; | |
| while ((test = next)) { | |
| next = test->fNext; | |
| if (!test->fBounded) { | |
| if (!this->removeSpan(test)) { | |
| return false; | |
| } | |
| } | |
| if (--safetyHatch < 0) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool SkTSect::extractCoincident( | |
| SkTSect* sect2, | |
| SkTSpan* first, SkTSpan* last, | |
| SkTSpan** result) { | |
| first = findCoincidentRun(first, &last); | |
| if (!first || !last) { | |
| *result = nullptr; | |
| return true; | |
| } | |
| // march outwards to find limit of coincidence from here to previous and next spans | |
| double startT = first->fStartT; | |
| double oppStartT SK_INIT_TO_AVOID_WARNING; | |
| double oppEndT SK_INIT_TO_AVOID_WARNING; | |
| SkTSpan* prev = first->fPrev; | |
| SkASSERT(first->fCoinStart.isMatch()); | |
| SkTSpan* oppFirst = first->findOppT(first->fCoinStart.perpT()); | |
| SkOPASSERT(last->fCoinEnd.isMatch()); | |
| bool oppMatched = first->fCoinStart.perpT() < first->fCoinEnd.perpT(); | |
| double coinStart; | |
| SkDEBUGCODE(double coinEnd); | |
| SkTSpan* cutFirst; | |
| if (prev && prev->fEndT == startT | |
| && this->binarySearchCoin(sect2, startT, prev->fStartT - startT, &coinStart, | |
| &oppStartT, &oppFirst) | |
| && prev->fStartT < coinStart && coinStart < startT | |
| && (cutFirst = prev->oppT(oppStartT))) { | |
| oppFirst = cutFirst; | |
| first = this->addSplitAt(prev, coinStart); | |
| first->markCoincident(); | |
| prev->fCoinEnd.markCoincident(); | |
| if (oppFirst->fStartT < oppStartT && oppStartT < oppFirst->fEndT) { | |
| SkTSpan* oppHalf = sect2->addSplitAt(oppFirst, oppStartT); | |
| if (oppMatched) { | |
| oppFirst->fCoinEnd.markCoincident(); | |
| oppHalf->markCoincident(); | |
| oppFirst = oppHalf; | |
| } else { | |
| oppFirst->markCoincident(); | |
| oppHalf->fCoinStart.markCoincident(); | |
| } | |
| } | |
| } else { | |
| if (!oppFirst) { | |
| return false; | |
| } | |
| SkDEBUGCODE(coinStart = first->fStartT); | |
| SkDEBUGCODE(oppStartT = oppMatched ? oppFirst->fStartT : oppFirst->fEndT); | |
| } | |
| // FIXME: incomplete : if we're not at the end, find end of coin | |
| SkTSpan* oppLast; | |
| SkOPASSERT(last->fCoinEnd.isMatch()); | |
| oppLast = last->findOppT(last->fCoinEnd.perpT()); | |
| SkDEBUGCODE(coinEnd = last->fEndT); | |
| #ifdef SK_DEBUG | |
| if (!this->globalState() || !this->globalState()->debugSkipAssert()) { | |
| oppEndT = oppMatched ? oppLast->fEndT : oppLast->fStartT; | |
| } | |
| #endif | |
| if (!oppMatched) { | |
| using std::swap; | |
| swap(oppFirst, oppLast); | |
| swap(oppStartT, oppEndT); | |
| } | |
| SkOPASSERT(oppStartT < oppEndT); | |
| SkASSERT(coinStart == first->fStartT); | |
| SkASSERT(coinEnd == last->fEndT); | |
| if (!oppFirst) { | |
| *result = nullptr; | |
| return true; | |
| } | |
| SkOPASSERT(oppStartT == oppFirst->fStartT); | |
| if (!oppLast) { | |
| *result = nullptr; | |
| return true; | |
| } | |
| SkOPASSERT(oppEndT == oppLast->fEndT); | |
| // reduce coincident runs to single entries | |
| this->validate(); | |
| sect2->validate(); | |
| bool deleteEmptySpans = this->updateBounded(first, last, oppFirst); | |
| deleteEmptySpans |= sect2->updateBounded(oppFirst, oppLast, first); | |
| this->removeSpanRange(first, last); | |
| sect2->removeSpanRange(oppFirst, oppLast); | |
| first->fEndT = last->fEndT; | |
| first->resetBounds(this->fCurve); | |
| first->fCoinStart.setPerp(fCurve, first->fStartT, first->pointFirst(), sect2->fCurve); | |
| first->fCoinEnd.setPerp(fCurve, first->fEndT, first->pointLast(), sect2->fCurve); | |
| oppStartT = first->fCoinStart.perpT(); | |
| oppEndT = first->fCoinEnd.perpT(); | |
| if (between(0, oppStartT, 1) && between(0, oppEndT, 1)) { | |
| if (!oppMatched) { | |
| using std::swap; | |
| swap(oppStartT, oppEndT); | |
| } | |
| oppFirst->fStartT = oppStartT; | |
| oppFirst->fEndT = oppEndT; | |
| oppFirst->resetBounds(sect2->fCurve); | |
| } | |
| this->validateBounded(); | |
| sect2->validateBounded(); | |
| last = first->fNext; | |
| if (!this->removeCoincident(first, false)) { | |
| return false; | |
| } | |
| if (!sect2->removeCoincident(oppFirst, true)) { | |
| return false; | |
| } | |
| if (deleteEmptySpans) { | |
| if (!this->deleteEmptySpans() || !sect2->deleteEmptySpans()) { | |
| *result = nullptr; | |
| return false; | |
| } | |
| } | |
| this->validate(); | |
| sect2->validate(); | |
| *result = last && !last->fDeleted && fHead && sect2->fHead ? last : nullptr; | |
| return true; | |
| } | |
| SkTSpan* SkTSect::findCoincidentRun( | |
| SkTSpan* first, SkTSpan** lastPtr) { | |
| SkTSpan* work = first; | |
| SkTSpan* lastCandidate = nullptr; | |
| first = nullptr; | |
| // find the first fully coincident span | |
| do { | |
| if (work->fCoinStart.isMatch()) { | |
| #if DEBUG_T_SECT | |
| work->validatePerpT(work->fCoinStart.perpT()); | |
| work->validatePerpPt(work->fCoinStart.perpT(), work->fCoinStart.perpPt()); | |
| #endif | |
| SkOPASSERT(work->hasOppT(work->fCoinStart.perpT())); | |
| if (!work->fCoinEnd.isMatch()) { | |
| break; | |
| } | |
| lastCandidate = work; | |
| if (!first) { | |
| first = work; | |
| } | |
| } else if (first && work->fCollapsed) { | |
| *lastPtr = lastCandidate; | |
| return first; | |
| } else { | |
| lastCandidate = nullptr; | |
| SkOPASSERT(!first); | |
| } | |
| if (work == *lastPtr) { | |
| return first; | |
| } | |
| work = work->fNext; | |
| if (!work) { | |
| return nullptr; | |
| } | |
| } while (true); | |
| if (lastCandidate) { | |
| *lastPtr = lastCandidate; | |
| } | |
| return first; | |
| } | |
| int SkTSect::intersects(SkTSpan* span, | |
| SkTSect* opp, | |
| SkTSpan* oppSpan, int* oppResult) { | |
| bool spanStart, oppStart; | |
| int hullResult = span->hullsIntersect(oppSpan, &spanStart, &oppStart); | |
| if (hullResult >= 0) { | |
| if (hullResult == 2) { // hulls have one point in common | |
| if (!span->fBounded || !span->fBounded->fNext) { | |
| SkASSERT(!span->fBounded || span->fBounded->fBounded == oppSpan); | |
| if (spanStart) { | |
| span->fEndT = span->fStartT; | |
| } else { | |
| span->fStartT = span->fEndT; | |
| } | |
| } else { | |
| hullResult = 1; | |
| } | |
| if (!oppSpan->fBounded || !oppSpan->fBounded->fNext) { | |
| if (oppSpan->fBounded && oppSpan->fBounded->fBounded != span) { | |
| return 0; | |
| } | |
| if (oppStart) { | |
| oppSpan->fEndT = oppSpan->fStartT; | |
| } else { | |
| oppSpan->fStartT = oppSpan->fEndT; | |
| } | |
| *oppResult = 2; | |
| } else { | |
| *oppResult = 1; | |
| } | |
| } else { | |
| *oppResult = 1; | |
| } | |
| return hullResult; | |
| } | |
| if (span->fIsLine && oppSpan->fIsLine) { | |
| SkIntersections i; | |
| int sects = this->linesIntersect(span, opp, oppSpan, &i); | |
| if (sects == 2) { | |
| return *oppResult = 1; | |
| } | |
| if (!sects) { | |
| return -1; | |
| } | |
| this->removedEndCheck(span); | |
| span->fStartT = span->fEndT = i[0][0]; | |
| opp->removedEndCheck(oppSpan); | |
| oppSpan->fStartT = oppSpan->fEndT = i[1][0]; | |
| return *oppResult = 2; | |
| } | |
| if (span->fIsLinear || oppSpan->fIsLinear) { | |
| return *oppResult = (int) span->linearsIntersect(oppSpan); | |
| } | |
| return *oppResult = 1; | |
| } | |
| template<typename SkTCurve> | |
| static bool is_parallel(const SkDLine& thisLine, const SkTCurve& opp) { | |
| if (!opp.IsConic()) { | |
| return false; // FIXME : breaks a lot of stuff now | |
| } | |
| int finds = 0; | |
| SkDLine thisPerp; | |
| thisPerp.fPts[0].fX = thisLine.fPts[1].fX + (thisLine.fPts[1].fY - thisLine.fPts[0].fY); | |
| thisPerp.fPts[0].fY = thisLine.fPts[1].fY + (thisLine.fPts[0].fX - thisLine.fPts[1].fX); | |
| thisPerp.fPts[1] = thisLine.fPts[1]; | |
| SkIntersections perpRayI; | |
| perpRayI.intersectRay(opp, thisPerp); | |
| for (int pIndex = 0; pIndex < perpRayI.used(); ++pIndex) { | |
| finds += perpRayI.pt(pIndex).approximatelyEqual(thisPerp.fPts[1]); | |
| } | |
| thisPerp.fPts[1].fX = thisLine.fPts[0].fX + (thisLine.fPts[1].fY - thisLine.fPts[0].fY); | |
| thisPerp.fPts[1].fY = thisLine.fPts[0].fY + (thisLine.fPts[0].fX - thisLine.fPts[1].fX); | |
| thisPerp.fPts[0] = thisLine.fPts[0]; | |
| perpRayI.intersectRay(opp, thisPerp); | |
| for (int pIndex = 0; pIndex < perpRayI.used(); ++pIndex) { | |
| finds += perpRayI.pt(pIndex).approximatelyEqual(thisPerp.fPts[0]); | |
| } | |
| return finds >= 2; | |
| } | |
| // while the intersection points are sufficiently far apart: | |
| // construct the tangent lines from the intersections | |
| // find the point where the tangent line intersects the opposite curve | |
| int SkTSect::linesIntersect(SkTSpan* span, | |
| SkTSect* opp, | |
| SkTSpan* oppSpan, SkIntersections* i) { | |
| SkIntersections thisRayI SkDEBUGCODE((span->fDebugGlobalState)); | |
| SkIntersections oppRayI SkDEBUGCODE((span->fDebugGlobalState)); | |
| SkDLine thisLine = {{ span->pointFirst(), span->pointLast() }}; | |
| SkDLine oppLine = {{ oppSpan->pointFirst(), oppSpan->pointLast() }}; | |
| int loopCount = 0; | |
| double bestDistSq = DBL_MAX; | |
| if (!thisRayI.intersectRay(opp->fCurve, thisLine)) { | |
| return 0; | |
| } | |
| if (!oppRayI.intersectRay(this->fCurve, oppLine)) { | |
| return 0; | |
| } | |
| // if the ends of each line intersect the opposite curve, the lines are coincident | |
| if (thisRayI.used() > 1) { | |
| int ptMatches = 0; | |
| for (int tIndex = 0; tIndex < thisRayI.used(); ++tIndex) { | |
| for (int lIndex = 0; lIndex < (int) std::size(thisLine.fPts); ++lIndex) { | |
| ptMatches += thisRayI.pt(tIndex).approximatelyEqual(thisLine.fPts[lIndex]); | |
| } | |
| } | |
| if (ptMatches == 2 || is_parallel(thisLine, opp->fCurve)) { | |
| return 2; | |
| } | |
| } | |
| if (oppRayI.used() > 1) { | |
| int ptMatches = 0; | |
| for (int oIndex = 0; oIndex < oppRayI.used(); ++oIndex) { | |
| for (int lIndex = 0; lIndex < (int) std::size(oppLine.fPts); ++lIndex) { | |
| ptMatches += oppRayI.pt(oIndex).approximatelyEqual(oppLine.fPts[lIndex]); | |
| } | |
| } | |
| if (ptMatches == 2|| is_parallel(oppLine, this->fCurve)) { | |
| return 2; | |
| } | |
| } | |
| do { | |
| // pick the closest pair of points | |
| double closest = DBL_MAX; | |
| int closeIndex SK_INIT_TO_AVOID_WARNING; | |
| int oppCloseIndex SK_INIT_TO_AVOID_WARNING; | |
| for (int index = 0; index < oppRayI.used(); ++index) { | |
| if (!roughly_between(span->fStartT, oppRayI[0][index], span->fEndT)) { | |
| continue; | |
| } | |
| for (int oIndex = 0; oIndex < thisRayI.used(); ++oIndex) { | |
| if (!roughly_between(oppSpan->fStartT, thisRayI[0][oIndex], oppSpan->fEndT)) { | |
| continue; | |
| } | |
| double distSq = thisRayI.pt(index).distanceSquared(oppRayI.pt(oIndex)); | |
| if (closest > distSq) { | |
| closest = distSq; | |
| closeIndex = index; | |
| oppCloseIndex = oIndex; | |
| } | |
| } | |
| } | |
| if (closest == DBL_MAX) { | |
| break; | |
| } | |
| const SkDPoint& oppIPt = thisRayI.pt(oppCloseIndex); | |
| const SkDPoint& iPt = oppRayI.pt(closeIndex); | |
| if (between(span->fStartT, oppRayI[0][closeIndex], span->fEndT) | |
| && between(oppSpan->fStartT, thisRayI[0][oppCloseIndex], oppSpan->fEndT) | |
| && oppIPt.approximatelyEqual(iPt)) { | |
| i->merge(oppRayI, closeIndex, thisRayI, oppCloseIndex); | |
| return i->used(); | |
| } | |
| double distSq = oppIPt.distanceSquared(iPt); | |
| if (bestDistSq < distSq || ++loopCount > 5) { | |
| return 0; | |
| } | |
| bestDistSq = distSq; | |
| double oppStart = oppRayI[0][closeIndex]; | |
| thisLine[0] = fCurve.ptAtT(oppStart); | |
| thisLine[1] = thisLine[0] + fCurve.dxdyAtT(oppStart); | |
| if (!thisRayI.intersectRay(opp->fCurve, thisLine)) { | |
| break; | |
| } | |
| double start = thisRayI[0][oppCloseIndex]; | |
| oppLine[0] = opp->fCurve.ptAtT(start); | |
| oppLine[1] = oppLine[0] + opp->fCurve.dxdyAtT(start); | |
| if (!oppRayI.intersectRay(this->fCurve, oppLine)) { | |
| break; | |
| } | |
| } while (true); | |
| // convergence may fail if the curves are nearly coincident | |
| SkTCoincident oCoinS, oCoinE; | |
| oCoinS.setPerp(opp->fCurve, oppSpan->fStartT, oppSpan->pointFirst(), fCurve); | |
| oCoinE.setPerp(opp->fCurve, oppSpan->fEndT, oppSpan->pointLast(), fCurve); | |
| double tStart = oCoinS.perpT(); | |
| double tEnd = oCoinE.perpT(); | |
| bool swap = tStart > tEnd; | |
| if (swap) { | |
| using std::swap; | |
| swap(tStart, tEnd); | |
| } | |
| tStart = std::max(tStart, span->fStartT); | |
| tEnd = std::min(tEnd, span->fEndT); | |
| if (tStart > tEnd) { | |
| return 0; | |
| } | |
| SkDVector perpS, perpE; | |
| if (tStart == span->fStartT) { | |
| SkTCoincident coinS; | |
| coinS.setPerp(fCurve, span->fStartT, span->pointFirst(), opp->fCurve); | |
| perpS = span->pointFirst() - coinS.perpPt(); | |
| } else if (swap) { | |
| perpS = oCoinE.perpPt() - oppSpan->pointLast(); | |
| } else { | |
| perpS = oCoinS.perpPt() - oppSpan->pointFirst(); | |
| } | |
| if (tEnd == span->fEndT) { | |
| SkTCoincident coinE; | |
| coinE.setPerp(fCurve, span->fEndT, span->pointLast(), opp->fCurve); | |
| perpE = span->pointLast() - coinE.perpPt(); | |
| } else if (swap) { | |
| perpE = oCoinS.perpPt() - oppSpan->pointFirst(); | |
| } else { | |
| perpE = oCoinE.perpPt() - oppSpan->pointLast(); | |
| } | |
| if (perpS.dot(perpE) >= 0) { | |
| return 0; | |
| } | |
| SkTCoincident coinW; | |
| double workT = tStart; | |
| double tStep = tEnd - tStart; | |
| SkDPoint workPt; | |
| do { | |
| tStep *= 0.5; | |
| if (precisely_zero(tStep)) { | |
| return 0; | |
| } | |
| workT += tStep; | |
| workPt = fCurve.ptAtT(workT); | |
| coinW.setPerp(fCurve, workT, workPt, opp->fCurve); | |
| double perpT = coinW.perpT(); | |
| if (coinW.isMatch() ? !between(oppSpan->fStartT, perpT, oppSpan->fEndT) : perpT < 0) { | |
| continue; | |
| } | |
| SkDVector perpW = workPt - coinW.perpPt(); | |
| if ((perpS.dot(perpW) >= 0) == (tStep < 0)) { | |
| tStep = -tStep; | |
| } | |
| if (workPt.approximatelyEqual(coinW.perpPt())) { | |
| break; | |
| } | |
| } while (true); | |
| double oppTTest = coinW.perpT(); | |
| if (!opp->fHead->contains(oppTTest)) { | |
| return 0; | |
| } | |
| i->setMax(1); | |
| i->insert(workT, oppTTest, workPt); | |
| return 1; | |
| } | |
| bool SkTSect::markSpanGone(SkTSpan* span) { | |
| if (--fActiveCount < 0) { | |
| return false; | |
| } | |
| span->fNext = fDeleted; | |
| fDeleted = span; | |
| SkOPASSERT(!span->fDeleted); | |
| span->fDeleted = true; | |
| return true; | |
| } | |
| bool SkTSect::matchedDirection(double t, const SkTSect* sect2, | |
| double t2) const { | |
| SkDVector dxdy = this->fCurve.dxdyAtT(t); | |
| SkDVector dxdy2 = sect2->fCurve.dxdyAtT(t2); | |
| return dxdy.dot(dxdy2) >= 0; | |
| } | |
| void SkTSect::matchedDirCheck(double t, const SkTSect* sect2, | |
| double t2, bool* calcMatched, bool* oppMatched) const { | |
| if (*calcMatched) { | |
| SkASSERT(*oppMatched == this->matchedDirection(t, sect2, t2)); | |
| } else { | |
| *oppMatched = this->matchedDirection(t, sect2, t2); | |
| *calcMatched = true; | |
| } | |
| } | |
| void SkTSect::mergeCoincidence(SkTSect* sect2) { | |
| double smallLimit = 0; | |
| do { | |
| // find the smallest unprocessed span | |
| SkTSpan* smaller = nullptr; | |
| SkTSpan* test = fCoincident; | |
| do { | |
| if (!test) { | |
| return; | |
| } | |
| if (test->fStartT < smallLimit) { | |
| continue; | |
| } | |
| if (smaller && smaller->fEndT < test->fStartT) { | |
| continue; | |
| } | |
| smaller = test; | |
| } while ((test = test->fNext)); | |
| if (!smaller) { | |
| return; | |
| } | |
| smallLimit = smaller->fEndT; | |
| // find next larger span | |
| SkTSpan* prior = nullptr; | |
| SkTSpan* larger = nullptr; | |
| SkTSpan* largerPrior = nullptr; | |
| test = fCoincident; | |
| do { | |
| if (test->fStartT < smaller->fEndT) { | |
| continue; | |
| } | |
| SkOPASSERT(test->fStartT != smaller->fEndT); | |
| if (larger && larger->fStartT < test->fStartT) { | |
| continue; | |
| } | |
| largerPrior = prior; | |
| larger = test; | |
| } while ((void) (prior = test), (test = test->fNext)); | |
| if (!larger) { | |
| continue; | |
| } | |
| // check middle t value to see if it is coincident as well | |
| double midT = (smaller->fEndT + larger->fStartT) / 2; | |
| SkDPoint midPt = fCurve.ptAtT(midT); | |
| SkTCoincident coin; | |
| coin.setPerp(fCurve, midT, midPt, sect2->fCurve); | |
| if (coin.isMatch()) { | |
| smaller->fEndT = larger->fEndT; | |
| smaller->fCoinEnd = larger->fCoinEnd; | |
| if (largerPrior) { | |
| largerPrior->fNext = larger->fNext; | |
| largerPrior->validate(); | |
| } else { | |
| fCoincident = larger->fNext; | |
| } | |
| } | |
| } while (true); | |
| } | |
| SkTSpan* SkTSect::prev( | |
| SkTSpan* span) const { | |
| SkTSpan* result = nullptr; | |
| SkTSpan* test = fHead; | |
| while (span != test) { | |
| result = test; | |
| test = test->fNext; | |
| SkASSERT(test); | |
| } | |
| return result; | |
| } | |
| void SkTSect::recoverCollapsed() { | |
| SkTSpan* deleted = fDeleted; | |
| while (deleted) { | |
| SkTSpan* delNext = deleted->fNext; | |
| if (deleted->fCollapsed) { | |
| SkTSpan** spanPtr = &fHead; | |
| while (*spanPtr && (*spanPtr)->fEndT <= deleted->fStartT) { | |
| spanPtr = &(*spanPtr)->fNext; | |
| } | |
| deleted->fNext = *spanPtr; | |
| *spanPtr = deleted; | |
| } | |
| deleted = delNext; | |
| } | |
| } | |
| void SkTSect::removeAllBut(const SkTSpan* keep, | |
| SkTSpan* span, SkTSect* opp) { | |
| const SkTSpanBounded* testBounded = span->fBounded; | |
| while (testBounded) { | |
| SkTSpan* bounded = testBounded->fBounded; | |
| const SkTSpanBounded* next = testBounded->fNext; | |
| // may have been deleted when opp did 'remove all but' | |
| if (bounded != keep && !bounded->fDeleted) { | |
| SkAssertResult(SkDEBUGCODE(!) span->removeBounded(bounded)); | |
| if (bounded->removeBounded(span)) { | |
| opp->removeSpan(bounded); | |
| } | |
| } | |
| testBounded = next; | |
| } | |
| SkASSERT(!span->fDeleted); | |
| SkASSERT(span->findOppSpan(keep)); | |
| SkASSERT(keep->findOppSpan(span)); | |
| } | |
| bool SkTSect::removeByPerpendicular(SkTSect* opp) { | |
| SkTSpan* test = fHead; | |
| SkTSpan* next; | |
| do { | |
| next = test->fNext; | |
| if (test->fCoinStart.perpT() < 0 || test->fCoinEnd.perpT() < 0) { | |
| continue; | |
| } | |
| SkDVector startV = test->fCoinStart.perpPt() - test->pointFirst(); | |
| SkDVector endV = test->fCoinEnd.perpPt() - test->pointLast(); | |
| #if DEBUG_T_SECT | |
| SkDebugf("%s startV=(%1.9g,%1.9g) endV=(%1.9g,%1.9g) dot=%1.9g\n", __FUNCTION__, | |
| startV.fX, startV.fY, endV.fX, endV.fY, startV.dot(endV)); | |
| #endif | |
| if (startV.dot(endV) <= 0) { | |
| continue; | |
| } | |
| if (!this->removeSpans(test, opp)) { | |
| return false; | |
| } | |
| } while ((test = next)); | |
| return true; | |
| } | |
| bool SkTSect::removeCoincident(SkTSpan* span, bool isBetween) { | |
| if (!this->unlinkSpan(span)) { | |
| return false; | |
| } | |
| if (isBetween || between(0, span->fCoinStart.perpT(), 1)) { | |
| --fActiveCount; | |
| span->fNext = fCoincident; | |
| fCoincident = span; | |
| } else { | |
| this->markSpanGone(span); | |
| } | |
| return true; | |
| } | |
| void SkTSect::removedEndCheck(SkTSpan* span) { | |
| if (!span->fStartT) { | |
| fRemovedStartT = true; | |
| } | |
| if (1 == span->fEndT) { | |
| fRemovedEndT = true; | |
| } | |
| } | |
| bool SkTSect::removeSpan(SkTSpan* span) {\ | |
| this->removedEndCheck(span); | |
| if (!this->unlinkSpan(span)) { | |
| return false; | |
| } | |
| return this->markSpanGone(span); | |
| } | |
| void SkTSect::removeSpanRange(SkTSpan* first, | |
| SkTSpan* last) { | |
| if (first == last) { | |
| return; | |
| } | |
| SkTSpan* span = first; | |
| SkASSERT(span); | |
| SkTSpan* final = last->fNext; | |
| SkTSpan* next = span->fNext; | |
| while ((span = next) && span != final) { | |
| next = span->fNext; | |
| this->markSpanGone(span); | |
| } | |
| if (final) { | |
| final->fPrev = first; | |
| } | |
| first->fNext = final; | |
| // world may not be ready for validation here | |
| first->validate(); | |
| } | |
| bool SkTSect::removeSpans(SkTSpan* span, | |
| SkTSect* opp) { | |
| SkTSpanBounded* bounded = span->fBounded; | |
| while (bounded) { | |
| SkTSpan* spanBounded = bounded->fBounded; | |
| SkTSpanBounded* next = bounded->fNext; | |
| if (span->removeBounded(spanBounded)) { // shuffles last into position 0 | |
| this->removeSpan(span); | |
| } | |
| if (spanBounded->removeBounded(span)) { | |
| opp->removeSpan(spanBounded); | |
| } | |
| if (span->fDeleted && opp->hasBounded(span)) { | |
| return false; | |
| } | |
| bounded = next; | |
| } | |
| return true; | |
| } | |
| SkTSpan* SkTSect::spanAtT(double t, | |
| SkTSpan** priorSpan) { | |
| SkTSpan* test = fHead; | |
| SkTSpan* prev = nullptr; | |
| while (test && test->fEndT < t) { | |
| prev = test; | |
| test = test->fNext; | |
| } | |
| *priorSpan = prev; | |
| return test && test->fStartT <= t ? test : nullptr; | |
| } | |
| SkTSpan* SkTSect::tail() { | |
| SkTSpan* result = fHead; | |
| SkTSpan* next = fHead; | |
| int safetyNet = 100000; | |
| while ((next = next->fNext)) { | |
| if (!--safetyNet) { | |
| return nullptr; | |
| } | |
| if (next->fEndT > result->fEndT) { | |
| result = next; | |
| } | |
| } | |
| return result; | |
| } | |
| /* Each span has a range of opposite spans it intersects. After the span is split in two, | |
| adjust the range to its new size */ | |
| bool SkTSect::trim(SkTSpan* span, | |
| SkTSect* opp) { | |
| FAIL_IF(!span->initBounds(fCurve)); | |
| const SkTSpanBounded* testBounded = span->fBounded; | |
| while (testBounded) { | |
| SkTSpan* test = testBounded->fBounded; | |
| const SkTSpanBounded* next = testBounded->fNext; | |
| int oppSects, sects = this->intersects(span, opp, test, &oppSects); | |
| if (sects >= 1) { | |
| if (oppSects == 2) { | |
| test->initBounds(opp->fCurve); | |
| opp->removeAllBut(span, test, this); | |
| } | |
| if (sects == 2) { | |
| span->initBounds(fCurve); | |
| this->removeAllBut(test, span, opp); | |
| return true; | |
| } | |
| } else { | |
| if (span->removeBounded(test)) { | |
| this->removeSpan(span); | |
| } | |
| if (test->removeBounded(span)) { | |
| opp->removeSpan(test); | |
| } | |
| } | |
| testBounded = next; | |
| } | |
| return true; | |
| } | |
| bool SkTSect::unlinkSpan(SkTSpan* span) { | |
| SkTSpan* prev = span->fPrev; | |
| SkTSpan* next = span->fNext; | |
| if (prev) { | |
| prev->fNext = next; | |
| if (next) { | |
| next->fPrev = prev; | |
| if (next->fStartT > next->fEndT) { | |
| return false; | |
| } | |
| // world may not be ready for validate here | |
| next->validate(); | |
| } | |
| } else { | |
| fHead = next; | |
| if (next) { | |
| next->fPrev = nullptr; | |
| } | |
| } | |
| return true; | |
| } | |
| bool SkTSect::updateBounded(SkTSpan* first, | |
| SkTSpan* last, SkTSpan* oppFirst) { | |
| SkTSpan* test = first; | |
| const SkTSpan* final = last->next(); | |
| bool deleteSpan = false; | |
| do { | |
| deleteSpan |= test->removeAllBounded(); | |
| } while ((test = test->fNext) != final && test); | |
| first->fBounded = nullptr; | |
| first->addBounded(oppFirst, &fHeap); | |
| // cannot call validate until remove span range is called | |
| return deleteSpan; | |
| } | |
| void SkTSect::validate() const { | |
| #if DEBUG_VALIDATE | |
| int count = 0; | |
| double last = 0; | |
| if (fHead) { | |
| const SkTSpan* span = fHead; | |
| SkASSERT(!span->fPrev); | |
| const SkTSpan* next; | |
| do { | |
| span->validate(); | |
| SkASSERT(span->fStartT >= last); | |
| last = span->fEndT; | |
| ++count; | |
| next = span->fNext; | |
| SkASSERT(next != span); | |
| } while ((span = next) != nullptr); | |
| } | |
| SkASSERT(count == fActiveCount); | |
| #endif | |
| #if DEBUG_T_SECT | |
| SkASSERT(fActiveCount <= fDebugAllocatedCount); | |
| int deletedCount = 0; | |
| const SkTSpan* deleted = fDeleted; | |
| while (deleted) { | |
| ++deletedCount; | |
| deleted = deleted->fNext; | |
| } | |
| const SkTSpan* coincident = fCoincident; | |
| while (coincident) { | |
| ++deletedCount; | |
| coincident = coincident->fNext; | |
| } | |
| SkASSERT(fActiveCount + deletedCount == fDebugAllocatedCount); | |
| #endif | |
| } | |
| void SkTSect::validateBounded() const { | |
| #if DEBUG_VALIDATE | |
| if (!fHead) { | |
| return; | |
| } | |
| const SkTSpan* span = fHead; | |
| do { | |
| span->validateBounded(); | |
| } while ((span = span->fNext) != nullptr); | |
| #endif | |
| } | |
| int SkTSect::EndsEqual(const SkTSect* sect1, | |
| const SkTSect* sect2, SkIntersections* intersections) { | |
| int zeroOneSet = 0; | |
| if (sect1->fCurve[0] == sect2->fCurve[0]) { | |
| zeroOneSet |= kZeroS1Set | kZeroS2Set; | |
| intersections->insert(0, 0, sect1->fCurve[0]); | |
| } | |
| if (sect1->fCurve[0] == sect2->pointLast()) { | |
| zeroOneSet |= kZeroS1Set | kOneS2Set; | |
| intersections->insert(0, 1, sect1->fCurve[0]); | |
| } | |
| if (sect1->pointLast() == sect2->fCurve[0]) { | |
| zeroOneSet |= kOneS1Set | kZeroS2Set; | |
| intersections->insert(1, 0, sect1->pointLast()); | |
| } | |
| if (sect1->pointLast() == sect2->pointLast()) { | |
| zeroOneSet |= kOneS1Set | kOneS2Set; | |
| intersections->insert(1, 1, sect1->pointLast()); | |
| } | |
| // check for zero | |
| if (!(zeroOneSet & (kZeroS1Set | kZeroS2Set)) | |
| && sect1->fCurve[0].approximatelyEqual(sect2->fCurve[0])) { | |
| zeroOneSet |= kZeroS1Set | kZeroS2Set; | |
| intersections->insertNear(0, 0, sect1->fCurve[0], sect2->fCurve[0]); | |
| } | |
| if (!(zeroOneSet & (kZeroS1Set | kOneS2Set)) | |
| && sect1->fCurve[0].approximatelyEqual(sect2->pointLast())) { | |
| zeroOneSet |= kZeroS1Set | kOneS2Set; | |
| intersections->insertNear(0, 1, sect1->fCurve[0], sect2->pointLast()); | |
| } | |
| // check for one | |
| if (!(zeroOneSet & (kOneS1Set | kZeroS2Set)) | |
| && sect1->pointLast().approximatelyEqual(sect2->fCurve[0])) { | |
| zeroOneSet |= kOneS1Set | kZeroS2Set; | |
| intersections->insertNear(1, 0, sect1->pointLast(), sect2->fCurve[0]); | |
| } | |
| if (!(zeroOneSet & (kOneS1Set | kOneS2Set)) | |
| && sect1->pointLast().approximatelyEqual(sect2->pointLast())) { | |
| zeroOneSet |= kOneS1Set | kOneS2Set; | |
| intersections->insertNear(1, 1, sect1->pointLast(), sect2->pointLast()); | |
| } | |
| return zeroOneSet; | |
| } | |
| struct SkClosestRecord { | |
| bool operator<(const SkClosestRecord& rh) const { | |
| return fClosest < rh.fClosest; | |
| } | |
| void addIntersection(SkIntersections* intersections) const { | |
| double r1t = fC1Index ? fC1Span->endT() : fC1Span->startT(); | |
| double r2t = fC2Index ? fC2Span->endT() : fC2Span->startT(); | |
| intersections->insert(r1t, r2t, fC1Span->part()[fC1Index]); | |
| } | |
| void findEnd(const SkTSpan* span1, const SkTSpan* span2, | |
| int c1Index, int c2Index) { | |
| const SkTCurve& c1 = span1->part(); | |
| const SkTCurve& c2 = span2->part(); | |
| if (!c1[c1Index].approximatelyEqual(c2[c2Index])) { | |
| return; | |
| } | |
| double dist = c1[c1Index].distanceSquared(c2[c2Index]); | |
| if (fClosest < dist) { | |
| return; | |
| } | |
| fC1Span = span1; | |
| fC2Span = span2; | |
| fC1StartT = span1->startT(); | |
| fC1EndT = span1->endT(); | |
| fC2StartT = span2->startT(); | |
| fC2EndT = span2->endT(); | |
| fC1Index = c1Index; | |
| fC2Index = c2Index; | |
| fClosest = dist; | |
| } | |
| bool matesWith(const SkClosestRecord& mate SkDEBUGPARAMS(SkIntersections* i)) const { | |
| SkOPOBJASSERT(i, fC1Span == mate.fC1Span || fC1Span->endT() <= mate.fC1Span->startT() | |
| || mate.fC1Span->endT() <= fC1Span->startT()); | |
| SkOPOBJASSERT(i, fC2Span == mate.fC2Span || fC2Span->endT() <= mate.fC2Span->startT() | |
| || mate.fC2Span->endT() <= fC2Span->startT()); | |
| return fC1Span == mate.fC1Span || fC1Span->endT() == mate.fC1Span->startT() | |
| || fC1Span->startT() == mate.fC1Span->endT() | |
| || fC2Span == mate.fC2Span | |
| || fC2Span->endT() == mate.fC2Span->startT() | |
| || fC2Span->startT() == mate.fC2Span->endT(); | |
| } | |
| void merge(const SkClosestRecord& mate) { | |
| fC1Span = mate.fC1Span; | |
| fC2Span = mate.fC2Span; | |
| fClosest = mate.fClosest; | |
| fC1Index = mate.fC1Index; | |
| fC2Index = mate.fC2Index; | |
| } | |
| void reset() { | |
| fClosest = FLT_MAX; | |
| SkDEBUGCODE(fC1Span = nullptr); | |
| SkDEBUGCODE(fC2Span = nullptr); | |
| SkDEBUGCODE(fC1Index = fC2Index = -1); | |
| } | |
| void update(const SkClosestRecord& mate) { | |
| fC1StartT = std::min(fC1StartT, mate.fC1StartT); | |
| fC1EndT = std::max(fC1EndT, mate.fC1EndT); | |
| fC2StartT = std::min(fC2StartT, mate.fC2StartT); | |
| fC2EndT = std::max(fC2EndT, mate.fC2EndT); | |
| } | |
| const SkTSpan* fC1Span; | |
| const SkTSpan* fC2Span; | |
| double fC1StartT; | |
| double fC1EndT; | |
| double fC2StartT; | |
| double fC2EndT; | |
| double fClosest; | |
| int fC1Index; | |
| int fC2Index; | |
| }; | |
| struct SkClosestSect { | |
| SkClosestSect() | |
| : fUsed(0) { | |
| fClosest.push_back().reset(); | |
| } | |
| bool find(const SkTSpan* span1, const SkTSpan* span2 | |
| SkDEBUGPARAMS(SkIntersections* i)) { | |
| SkClosestRecord* record = &fClosest[fUsed]; | |
| record->findEnd(span1, span2, 0, 0); | |
| record->findEnd(span1, span2, 0, span2->part().pointLast()); | |
| record->findEnd(span1, span2, span1->part().pointLast(), 0); | |
| record->findEnd(span1, span2, span1->part().pointLast(), span2->part().pointLast()); | |
| if (record->fClosest == FLT_MAX) { | |
| return false; | |
| } | |
| for (int index = 0; index < fUsed; ++index) { | |
| SkClosestRecord* test = &fClosest[index]; | |
| if (test->matesWith(*record SkDEBUGPARAMS(i))) { | |
| if (test->fClosest > record->fClosest) { | |
| test->merge(*record); | |
| } | |
| test->update(*record); | |
| record->reset(); | |
| return false; | |
| } | |
| } | |
| ++fUsed; | |
| fClosest.push_back().reset(); | |
| return true; | |
| } | |
| void finish(SkIntersections* intersections) const { | |
| STArray<SkDCubic::kMaxIntersections * 3, | |
| const SkClosestRecord*, true> closestPtrs; | |
| for (int index = 0; index < fUsed; ++index) { | |
| closestPtrs.push_back(&fClosest[index]); | |
| } | |
| SkTQSort<const SkClosestRecord>(closestPtrs.begin(), closestPtrs.end()); | |
| for (int index = 0; index < fUsed; ++index) { | |
| const SkClosestRecord* test = closestPtrs[index]; | |
| test->addIntersection(intersections); | |
| } | |
| } | |
| // this is oversized so that an extra records can merge into final one | |
| STArray<SkDCubic::kMaxIntersections * 2, SkClosestRecord, true> fClosest; | |
| int fUsed; | |
| }; | |
| // returns true if the rect is too small to consider | |
| void SkTSect::BinarySearch(SkTSect* sect1, | |
| SkTSect* sect2, SkIntersections* intersections) { | |
| #if DEBUG_T_SECT_DUMP > 1 | |
| gDumpTSectNum = 0; | |
| #endif | |
| SkDEBUGCODE(sect1->fOppSect = sect2); | |
| SkDEBUGCODE(sect2->fOppSect = sect1); | |
| intersections->reset(); | |
| intersections->setMax(sect1->fCurve.maxIntersections() + 4); // give extra for slop | |
| SkTSpan* span1 = sect1->fHead; | |
| SkTSpan* span2 = sect2->fHead; | |
| int oppSect, sect = sect1->intersects(span1, sect2, span2, &oppSect); | |
| // SkASSERT(between(0, sect, 2)); | |
| if (!sect) { | |
| return; | |
| } | |
| if (sect == 2 && oppSect == 2) { | |
| (void) EndsEqual(sect1, sect2, intersections); | |
| return; | |
| } | |
| span1->addBounded(span2, §1->fHeap); | |
| span2->addBounded(span1, §2->fHeap); | |
| const int kMaxCoinLoopCount = 8; | |
| int coinLoopCount = kMaxCoinLoopCount; | |
| double start1s SK_INIT_TO_AVOID_WARNING; | |
| double start1e SK_INIT_TO_AVOID_WARNING; | |
| do { | |
| // find the largest bounds | |
| SkTSpan* largest1 = sect1->boundsMax(); | |
| if (!largest1) { | |
| if (sect1->fHung) { | |
| return; | |
| } | |
| break; | |
| } | |
| SkTSpan* largest2 = sect2->boundsMax(); | |
| // split it | |
| if (!largest2 || (largest1 && (largest1->fBoundsMax > largest2->fBoundsMax | |
| || (!largest1->fCollapsed && largest2->fCollapsed)))) { | |
| if (sect2->fHung) { | |
| return; | |
| } | |
| if (largest1->fCollapsed) { | |
| break; | |
| } | |
| sect1->resetRemovedEnds(); | |
| sect2->resetRemovedEnds(); | |
| // trim parts that don't intersect the opposite | |
| SkTSpan* half1 = sect1->addOne(); | |
| SkDEBUGCODE(half1->debugSetGlobalState(sect1->globalState())); | |
| if (!half1->split(largest1, §1->fHeap)) { | |
| break; | |
| } | |
| if (!sect1->trim(largest1, sect2)) { | |
| SkOPOBJASSERT(intersections, 0); | |
| return; | |
| } | |
| if (!sect1->trim(half1, sect2)) { | |
| SkOPOBJASSERT(intersections, 0); | |
| return; | |
| } | |
| } else { | |
| if (largest2->fCollapsed) { | |
| break; | |
| } | |
| sect1->resetRemovedEnds(); | |
| sect2->resetRemovedEnds(); | |
| // trim parts that don't intersect the opposite | |
| SkTSpan* half2 = sect2->addOne(); | |
| SkDEBUGCODE(half2->debugSetGlobalState(sect2->globalState())); | |
| if (!half2->split(largest2, §2->fHeap)) { | |
| break; | |
| } | |
| if (!sect2->trim(largest2, sect1)) { | |
| SkOPOBJASSERT(intersections, 0); | |
| return; | |
| } | |
| if (!sect2->trim(half2, sect1)) { | |
| SkOPOBJASSERT(intersections, 0); | |
| return; | |
| } | |
| } | |
| sect1->validate(); | |
| sect2->validate(); | |
| #if DEBUG_T_SECT_LOOP_COUNT | |
| intersections->debugBumpLoopCount(SkIntersections::kIterations_DebugLoop); | |
| #endif | |
| // if there are 9 or more continuous spans on both sects, suspect coincidence | |
| if (sect1->fActiveCount >= COINCIDENT_SPAN_COUNT | |
| && sect2->fActiveCount >= COINCIDENT_SPAN_COUNT) { | |
| if (coinLoopCount == kMaxCoinLoopCount) { | |
| start1s = sect1->fHead->fStartT; | |
| start1e = sect1->tail()->fEndT; | |
| } | |
| if (!sect1->coincidentCheck(sect2)) { | |
| return; | |
| } | |
| sect1->validate(); | |
| sect2->validate(); | |
| #if DEBUG_T_SECT_LOOP_COUNT | |
| intersections->debugBumpLoopCount(SkIntersections::kCoinCheck_DebugLoop); | |
| #endif | |
| if (!--coinLoopCount && sect1->fHead && sect2->fHead) { | |
| /* All known working cases resolve in two tries. Sadly, cubicConicTests[0] | |
| gets stuck in a loop. It adds an extension to allow a coincident end | |
| perpendicular to track its intersection in the opposite curve. However, | |
| the bounding box of the extension does not intersect the original curve, | |
| so the extension is discarded, only to be added again the next time around. */ | |
| sect1->coincidentForce(sect2, start1s, start1e); | |
| sect1->validate(); | |
| sect2->validate(); | |
| } | |
| } | |
| if (sect1->fActiveCount >= COINCIDENT_SPAN_COUNT | |
| && sect2->fActiveCount >= COINCIDENT_SPAN_COUNT) { | |
| if (!sect1->fHead) { | |
| return; | |
| } | |
| sect1->computePerpendiculars(sect2, sect1->fHead, sect1->tail()); | |
| if (!sect2->fHead) { | |
| return; | |
| } | |
| sect2->computePerpendiculars(sect1, sect2->fHead, sect2->tail()); | |
| if (!sect1->removeByPerpendicular(sect2)) { | |
| return; | |
| } | |
| sect1->validate(); | |
| sect2->validate(); | |
| #if DEBUG_T_SECT_LOOP_COUNT | |
| intersections->debugBumpLoopCount(SkIntersections::kComputePerp_DebugLoop); | |
| #endif | |
| if (sect1->collapsed() > sect1->fCurve.maxIntersections()) { | |
| break; | |
| } | |
| } | |
| #if DEBUG_T_SECT_DUMP | |
| sect1->dumpBoth(sect2); | |
| #endif | |
| if (!sect1->fHead || !sect2->fHead) { | |
| break; | |
| } | |
| } while (true); | |
| SkTSpan* coincident = sect1->fCoincident; | |
| if (coincident) { | |
| // if there is more than one coincident span, check loosely to see if they should be joined | |
| if (coincident->fNext) { | |
| sect1->mergeCoincidence(sect2); | |
| coincident = sect1->fCoincident; | |
| } | |
| SkASSERT(sect2->fCoincident); // courtesy check : coincidence only looks at sect 1 | |
| do { | |
| if (!coincident) { | |
| return; | |
| } | |
| if (!coincident->fCoinStart.isMatch()) { | |
| continue; | |
| } | |
| if (!coincident->fCoinEnd.isMatch()) { | |
| continue; | |
| } | |
| double perpT = coincident->fCoinStart.perpT(); | |
| if (perpT < 0) { | |
| return; | |
| } | |
| int index = intersections->insertCoincident(coincident->fStartT, | |
| perpT, coincident->pointFirst()); | |
| if ((intersections->insertCoincident(coincident->fEndT, | |
| coincident->fCoinEnd.perpT(), | |
| coincident->pointLast()) < 0) && index >= 0) { | |
| intersections->clearCoincidence(index); | |
| } | |
| } while ((coincident = coincident->fNext)); | |
| } | |
| int zeroOneSet = EndsEqual(sect1, sect2, intersections); | |
| // if (!sect1->fHead || !sect2->fHead) { | |
| // if the final iteration contains an end (0 or 1), | |
| if (sect1->fRemovedStartT && !(zeroOneSet & kZeroS1Set)) { | |
| SkTCoincident perp; // intersect perpendicular with opposite curve | |
| perp.setPerp(sect1->fCurve, 0, sect1->fCurve[0], sect2->fCurve); | |
| if (perp.isMatch()) { | |
| intersections->insert(0, perp.perpT(), perp.perpPt()); | |
| } | |
| } | |
| if (sect1->fRemovedEndT && !(zeroOneSet & kOneS1Set)) { | |
| SkTCoincident perp; | |
| perp.setPerp(sect1->fCurve, 1, sect1->pointLast(), sect2->fCurve); | |
| if (perp.isMatch()) { | |
| intersections->insert(1, perp.perpT(), perp.perpPt()); | |
| } | |
| } | |
| if (sect2->fRemovedStartT && !(zeroOneSet & kZeroS2Set)) { | |
| SkTCoincident perp; | |
| perp.setPerp(sect2->fCurve, 0, sect2->fCurve[0], sect1->fCurve); | |
| if (perp.isMatch()) { | |
| intersections->insert(perp.perpT(), 0, perp.perpPt()); | |
| } | |
| } | |
| if (sect2->fRemovedEndT && !(zeroOneSet & kOneS2Set)) { | |
| SkTCoincident perp; | |
| perp.setPerp(sect2->fCurve, 1, sect2->pointLast(), sect1->fCurve); | |
| if (perp.isMatch()) { | |
| intersections->insert(perp.perpT(), 1, perp.perpPt()); | |
| } | |
| } | |
| // } | |
| if (!sect1->fHead || !sect2->fHead) { | |
| return; | |
| } | |
| sect1->recoverCollapsed(); | |
| sect2->recoverCollapsed(); | |
| SkTSpan* result1 = sect1->fHead; | |
| // check heads and tails for zero and ones and insert them if we haven't already done so | |
| const SkTSpan* head1 = result1; | |
| if (!(zeroOneSet & kZeroS1Set) && approximately_less_than_zero(head1->fStartT)) { | |
| const SkDPoint& start1 = sect1->fCurve[0]; | |
| if (head1->isBounded()) { | |
| double t = head1->closestBoundedT(start1); | |
| if (sect2->fCurve.ptAtT(t).approximatelyEqual(start1)) { | |
| intersections->insert(0, t, start1); | |
| } | |
| } | |
| } | |
| const SkTSpan* head2 = sect2->fHead; | |
| if (!(zeroOneSet & kZeroS2Set) && approximately_less_than_zero(head2->fStartT)) { | |
| const SkDPoint& start2 = sect2->fCurve[0]; | |
| if (head2->isBounded()) { | |
| double t = head2->closestBoundedT(start2); | |
| if (sect1->fCurve.ptAtT(t).approximatelyEqual(start2)) { | |
| intersections->insert(t, 0, start2); | |
| } | |
| } | |
| } | |
| if (!(zeroOneSet & kOneS1Set)) { | |
| const SkTSpan* tail1 = sect1->tail(); | |
| if (!tail1) { | |
| return; | |
| } | |
| if (approximately_greater_than_one(tail1->fEndT)) { | |
| const SkDPoint& end1 = sect1->pointLast(); | |
| if (tail1->isBounded()) { | |
| double t = tail1->closestBoundedT(end1); | |
| if (sect2->fCurve.ptAtT(t).approximatelyEqual(end1)) { | |
| intersections->insert(1, t, end1); | |
| } | |
| } | |
| } | |
| } | |
| if (!(zeroOneSet & kOneS2Set)) { | |
| const SkTSpan* tail2 = sect2->tail(); | |
| if (!tail2) { | |
| return; | |
| } | |
| if (approximately_greater_than_one(tail2->fEndT)) { | |
| const SkDPoint& end2 = sect2->pointLast(); | |
| if (tail2->isBounded()) { | |
| double t = tail2->closestBoundedT(end2); | |
| if (sect1->fCurve.ptAtT(t).approximatelyEqual(end2)) { | |
| intersections->insert(t, 1, end2); | |
| } | |
| } | |
| } | |
| } | |
| SkClosestSect closest; | |
| do { | |
| while (result1 && result1->fCoinStart.isMatch() && result1->fCoinEnd.isMatch()) { | |
| result1 = result1->fNext; | |
| } | |
| if (!result1) { | |
| break; | |
| } | |
| SkTSpan* result2 = sect2->fHead; | |
| while (result2) { | |
| closest.find(result1, result2 SkDEBUGPARAMS(intersections)); | |
| result2 = result2->fNext; | |
| } | |
| } while ((result1 = result1->fNext)); | |
| closest.finish(intersections); | |
| // if there is more than one intersection and it isn't already coincident, check | |
| int last = intersections->used() - 1; | |
| for (int index = 0; index < last; ) { | |
| if (intersections->isCoincident(index) && intersections->isCoincident(index + 1)) { | |
| ++index; | |
| continue; | |
| } | |
| double midT = ((*intersections)[0][index] + (*intersections)[0][index + 1]) / 2; | |
| SkDPoint midPt = sect1->fCurve.ptAtT(midT); | |
| // intersect perpendicular with opposite curve | |
| SkTCoincident perp; | |
| perp.setPerp(sect1->fCurve, midT, midPt, sect2->fCurve); | |
| if (!perp.isMatch()) { | |
| ++index; | |
| continue; | |
| } | |
| if (intersections->isCoincident(index)) { | |
| intersections->removeOne(index); | |
| --last; | |
| } else if (intersections->isCoincident(index + 1)) { | |
| intersections->removeOne(index + 1); | |
| --last; | |
| } else { | |
| intersections->setCoincident(index++); | |
| } | |
| intersections->setCoincident(index); | |
| } | |
| SkOPOBJASSERT(intersections, intersections->used() <= sect1->fCurve.maxIntersections()); | |
| } | |
| int SkIntersections::intersect(const SkDQuad& q1, const SkDQuad& q2) { | |
| SkTQuad quad1(q1); | |
| SkTQuad quad2(q2); | |
| SkTSect sect1(quad1 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); | |
| SkTSect sect2(quad2 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); | |
| SkTSect::BinarySearch(§1, §2, this); | |
| return used(); | |
| } | |
| int SkIntersections::intersect(const SkDConic& c, const SkDQuad& q) { | |
| SkTConic conic(c); | |
| SkTQuad quad(q); | |
| SkTSect sect1(conic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); | |
| SkTSect sect2(quad SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); | |
| SkTSect::BinarySearch(§1, §2, this); | |
| return used(); | |
| } | |
| int SkIntersections::intersect(const SkDConic& c1, const SkDConic& c2) { | |
| SkTConic conic1(c1); | |
| SkTConic conic2(c2); | |
| SkTSect sect1(conic1 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); | |
| SkTSect sect2(conic2 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); | |
| SkTSect::BinarySearch(§1, §2, this); | |
| return used(); | |
| } | |
| int SkIntersections::intersect(const SkDCubic& c, const SkDQuad& q) { | |
| SkTCubic cubic(c); | |
| SkTQuad quad(q); | |
| SkTSect sect1(cubic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); | |
| SkTSect sect2(quad SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); | |
| SkTSect::BinarySearch(§1, §2, this); | |
| return used(); | |
| } | |
| int SkIntersections::intersect(const SkDCubic& cu, const SkDConic& co) { | |
| SkTCubic cubic(cu); | |
| SkTConic conic(co); | |
| SkTSect sect1(cubic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); | |
| SkTSect sect2(conic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); | |
| SkTSect::BinarySearch(§1, §2, this); | |
| return used(); | |
| } | |
| int SkIntersections::intersect(const SkDCubic& c1, const SkDCubic& c2) { | |
| SkTCubic cubic1(c1); | |
| SkTCubic cubic2(c2); | |
| SkTSect sect1(cubic1 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); | |
| SkTSect sect2(cubic2 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); | |
| SkTSect::BinarySearch(§1, §2, this); | |
| return used(); | |
| } |