// A tiny interval may indicate an undiscovered coincidence. Find and fix. static void checkTiny(SkTArray<SkOpContour*, true>* contourList) { int contourCount = (*contourList).count(); for (int cTest = 0; cTest < contourCount; ++cTest) { SkOpContour* contour = (*contourList)[cTest]; contour->checkTiny(); } }
static void joinCoincidence(SkTArray<SkOpContour*, true>* contourList) { int contourCount = (*contourList).count(); for (int cTest = 0; cTest < contourCount; ++cTest) { SkOpContour* contour = (*contourList)[cTest]; contour->joinCoincidence(); } }
static void fixOtherTIndex(SkTArray<SkOpContour*, true>* contourList) { int contourCount = (*contourList).count(); for (int cTest = 0; cTest < contourCount; ++cTest) { SkOpContour* contour = (*contourList)[cTest]; contour->fixOtherTIndex(); } }
bool TightBounds(const SkPath& path, SkRect* result) { SkChunkAlloc allocator(4096); // FIXME: constant-ize, tune SkOpContour contour; SkOpContourHead* contourList = static_cast<SkOpContourHead*>(&contour); SkOpGlobalState globalState(contourList, &allocator SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr)); // turn path into list of segments SkScalar scaleFactor = ScaleFactor(path); SkPath scaledPath; const SkPath* workingPath; if (scaleFactor > SK_Scalar1) { ScalePath(path, 1.f / scaleFactor, &scaledPath); workingPath = &scaledPath; } else { workingPath = &path; } SkOpEdgeBuilder builder(*workingPath, &contour, &globalState); if (!builder.finish()) { return false; } if (!SortContourList(&contourList, false, false)) { result->setEmpty(); return true; } SkOpContour* current = contourList; SkPathOpsBounds bounds = current->bounds(); while ((current = current->next())) { bounds.add(current->bounds()); } *result = bounds; return true; }
static void sortSegments(SkTArray<SkOpContour*, true>* contourList) { int contourCount = (*contourList).count(); for (int cTest = 0; cTest < contourCount; ++cTest) { SkOpContour* contour = (*contourList)[cTest]; contour->sortSegments(); } }
static bool missingCoincidence(SkOpContourHead* contourList) { SkOpContour* contour = contourList; bool result = false; do { result |= contour->missingCoincidence(); } while ((contour = contour->next())); return result; }
static void checkEnds(SkTArray<SkOpContour*, true>* contourList) { // it's hard to determine if the end of a cubic or conic nearly intersects another curve. // instead, look to see if the connecting curve intersected at that same end. int contourCount = (*contourList).count(); for (int cTest = 0; cTest < contourCount; ++cTest) { SkOpContour* contour = (*contourList)[cTest]; contour->checkEnds(); } }
static bool missingCoincidence(SkOpContourHead* contourList, SkOpCoincidence* coincidence, SkChunkAlloc* allocator) { SkOpContour* contour = contourList; bool result = false; do { result |= contour->missingCoincidence(coincidence, allocator); } while ((contour = contour->next())); return result; }
static bool moveMultiples(SkOpContourHead* contourList) { SkOpContour* contour = contourList; do { if (!contour->moveMultiples()) { return false; } } while ((contour = contour->next())); return true; }
static void alignMultiples(SkTArray<SkOpContour*, true>* contourList, SkTDArray<SkOpSegment::AlignedSpan>* aligned) { int contourCount = (*contourList).count(); for (int cTest = 0; cTest < contourCount; ++cTest) { SkOpContour* contour = (*contourList)[cTest]; if (contour->hasMultiples()) { contour->alignMultiples(aligned); } } }
static bool calcAngles(SkTArray<SkOpContour*, true>* contourList) { int contourCount = (*contourList).count(); for (int cTest = 0; cTest < contourCount; ++cTest) { SkOpContour* contour = (*contourList)[cTest]; if (!contour->calcAngles()) { return false; } } return true; }
static bool checkMultiples(SkTArray<SkOpContour*, true>* contourList) { bool hasMultiples = false; int contourCount = (*contourList).count(); for (int cTest = 0; cTest < contourCount; ++cTest) { SkOpContour* contour = (*contourList)[cTest]; contour->checkMultiples(); hasMultiples |= contour->hasMultiples(); } return hasMultiples; }
static void alignCoincidence(SkTArray<SkOpContour*, true>* contourList, const SkTDArray<SkOpSegment::AlignedSpan>& aligned) { int contourCount = (*contourList).count(); for (int cTest = 0; cTest < contourCount; ++cTest) { SkOpContour* contour = (*contourList)[cTest]; int count = aligned.count(); for (int index = 0; index < count; ++index) { contour->alignCoincidence(aligned[index]); } } }
SkOpSegment* FindUndone(SkOpContourHead* contourList, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr) { SkOpSegment* result; SkOpContour* contour = contourList; do { result = contour->undoneSegment(startPtr, endPtr); if (result) { return result; } } while ((contour = contour->next())); return nullptr; }
bool SkOpEdgeBuilder::finish() { fOperand = false; if (fUnparseable || !walk()) { return false; } complete(); SkOpContour* contour = fContourBuilder.contour(); if (contour && !contour->count()) { fContoursHead->remove(contour); } return true; }
SkOpSegment* FindUndone(SkTArray<SkOpContour*, true>& contourList, int* start, int* end) { int contourCount = contourList.count(); SkOpSegment* result; for (int cIndex = 0; cIndex < contourCount; ++cIndex) { SkOpContour* contour = contourList[cIndex]; result = contour->undoneSegment(start, end); if (result) { return result; } } return NULL; }
bool SortContourList(SkOpContourHead** contourList, bool evenOdd, bool oppEvenOdd) { SkTDArray<SkOpContour* > list; SkOpContour* contour = *contourList; do { if (contour->count()) { contour->setOppXor(contour->operand() ? evenOdd : oppEvenOdd); *list.append() = contour; } } while ((contour = contour->next())); int count = list.count(); if (!count) { return false; } if (count > 1) { SkTQSort<SkOpContour>(list.begin(), list.end() - 1); } contour = list[0]; SkOpContourHead* contourHead = static_cast<SkOpContourHead*>(contour); contour->globalState()->setContourHead(contourHead); *contourList = contourHead; for (int index = 1; index < count; ++index) { SkOpContour* next = list[index]; contour->setNext(next); contour = next; } contour->setNext(nullptr); return true; }
// FIXME : add this as a member of SkPath void Simplify(const SkPath& path, SkPath* result) { #if DEBUG_SORT || DEBUG_SWAP_TOP gDebugSortCount = gDebugSortCountDefault; #endif // returns 1 for evenodd, -1 for winding, regardless of inverse-ness result->reset(); result->setFillType(SkPath::kEvenOdd_FillType); SkPathWriter simple(*result); // turn path into list of segments SkTArray<SkOpContour> contours; SkOpEdgeBuilder builder(path, contours); builder.finish(); SkTDArray<SkOpContour*> contourList; MakeContourList(contours, contourList, false, false); SkOpContour** currentPtr = contourList.begin(); if (!currentPtr) { return; } SkOpContour** listEnd = contourList.end(); // find all intersections between segments do { SkOpContour** nextPtr = currentPtr; SkOpContour* current = *currentPtr++; if (current->containsCubics()) { AddSelfIntersectTs(current); } SkOpContour* next; do { next = *nextPtr++; } while (AddIntersectTs(current, next) && nextPtr != listEnd); } while (currentPtr != listEnd); // eat through coincident edges CoincidenceCheck(&contourList, 0); FixOtherTIndex(&contourList); SortSegments(&contourList); #if DEBUG_ACTIVE_SPANS DebugShowActiveSpans(contourList); #endif // construct closed contours if (builder.xorMask() == kWinding_PathOpsMask ? bridgeWinding(contourList, &simple) : !bridgeXor(contourList, &simple)) { // if some edges could not be resolved, assemble remaining fragments SkPath temp; temp.setFillType(SkPath::kEvenOdd_FillType); SkPathWriter assembled(temp); Assemble(simple, &assembled); *result = *assembled.nativePath(); } }
static void skipVertical(const SkTArray<SkOpContour*, true>& contourList, SkOpSegment** current, int* index, int* endIndex) { if (!(*current)->isVertical(*index, *endIndex)) { return; } int contourCount = contourList.count(); for (int cIndex = 0; cIndex < contourCount; ++cIndex) { SkOpContour* contour = contourList[cIndex]; if (contour->done()) { continue; } *current = contour->nonVerticalSegment(index, endIndex); if (*current) { return; } } }
static SkOpSegment* findTopSegment(const SkTArray<SkOpContour*, true>& contourList, int* index, int* endIndex, SkPoint* topLeft, bool* unsortable, bool* done, bool firstPass) { SkOpSegment* result; const SkOpSegment* lastTopStart = NULL; int lastIndex = -1, lastEndIndex = -1; do { SkPoint bestXY = {SK_ScalarMax, SK_ScalarMax}; int contourCount = contourList.count(); SkOpSegment* topStart = NULL; *done = true; for (int cIndex = 0; cIndex < contourCount; ++cIndex) { SkOpContour* contour = contourList[cIndex]; if (contour->done()) { continue; } const SkPathOpsBounds& bounds = contour->bounds(); if (bounds.fBottom < topLeft->fY) { *done = false; continue; } if (bounds.fBottom == topLeft->fY && bounds.fRight < topLeft->fX) { *done = false; continue; } contour->topSortableSegment(*topLeft, &bestXY, &topStart); if (!contour->done()) { *done = false; } } if (!topStart) { return NULL; } *topLeft = bestXY; result = topStart->findTop(index, endIndex, unsortable, firstPass); if (!result) { if (lastTopStart == topStart && lastIndex == *index && lastEndIndex == *endIndex) { *done = true; return NULL; } lastTopStart = topStart; lastIndex = *index; lastEndIndex = *endIndex; } } while (!result); return result; }
DEF_TEST(PathOpsAngleCircle, reporter) { SkChunkAlloc allocator(4096); SkOpContour contour; SkOpGlobalState state(NULL PATH_OPS_DEBUG_PARAMS(&contour)); contour.init(&state, false, false); for (int index = 0; index < circleDataSetSize; ++index) { CircleData& data = circleDataSet[index]; for (int idx2 = 0; idx2 < data.fPtCount; ++idx2) { data.fShortPts[idx2] = data.fPts.fPts[idx2].asSkPoint(); } switch (data.fPtCount) { case 2: contour.addLine(data.fShortPts, &allocator); break; case 3: contour.addQuad(data.fShortPts, &allocator); break; case 4: contour.addCubic(data.fShortPts, &allocator); break; } } SkOpSegment* first = contour.first(); first->debugAddAngle(0, 1, &allocator); SkOpSegment* next = first->next(); next->debugAddAngle(0, 1, &allocator); PathOpsAngleTester::Orderable(*first->debugLastAngle(), *next->debugLastAngle()); }
bool TightBounds(const SkPath& path, SkRect* result) { SkChunkAlloc allocator(4096); // FIXME: constant-ize, tune SkOpContour contour; SkOpContourHead* contourList = static_cast<SkOpContourHead*>(&contour); SkOpGlobalState globalState(nullptr, contourList SkDEBUGPARAMS(nullptr)); // turn path into list of segments SkOpEdgeBuilder builder(path, &contour, &allocator, &globalState); if (!builder.finish(&allocator)) { return false; } if (!SortContourList(&contourList, false, false)) { result->setEmpty(); return true; } SkOpContour* current = contourList; SkPathOpsBounds bounds = current->bounds(); while ((current = current->next())) { bounds.add(current->bounds()); } *result = bounds; return true; }
// resolve any coincident pairs found while intersecting, and // see if coincidence is formed by clipping non-concident segments void CoincidenceCheck(SkTArray<SkOpContour*, true>* contourList, int total) { int contourCount = (*contourList).count(); for (int cIndex = 0; cIndex < contourCount; ++cIndex) { SkOpContour* contour = (*contourList)[cIndex]; contour->addCoincidentPoints(); } for (int cIndex = 0; cIndex < contourCount; ++cIndex) { SkOpContour* contour = (*contourList)[cIndex]; contour->calcCoincidentWinding(); } for (int cIndex = 0; cIndex < contourCount; ++cIndex) { SkOpContour* contour = (*contourList)[cIndex]; contour->calcPartialCoincidentWinding(); } }
static void moveMultiples(SkOpContourHead* contourList) { SkOpContour* contour = contourList; do { contour->moveMultiples(); } while ((contour = contour->next())); }
static void moveNearby(SkOpContourHead* contourList) { SkOpContour* contour = contourList; do { contour->moveNearby(); } while ((contour = contour->next())); }
static void sortAngles(SkOpContourHead* contourList) { SkOpContour* contour = contourList; do { contour->sortAngles(); } while ((contour = contour->next())); }
bool FixWinding(SkPath* path) { SkPath::FillType fillType = path->getFillType(); if (fillType == SkPath::kInverseEvenOdd_FillType) { fillType = SkPath::kInverseWinding_FillType; } else if (fillType == SkPath::kEvenOdd_FillType) { fillType = SkPath::kWinding_FillType; } SkPathPriv::FirstDirection dir; if (one_contour(*path) && SkPathPriv::CheapComputeFirstDirection(*path, &dir)) { if (dir != SkPathPriv::kCCW_FirstDirection) { SkPath temp; temp.reverseAddPath(*path); *path = temp; } path->setFillType(fillType); return true; } SkChunkAlloc allocator(4096); SkOpContourHead contourHead; SkOpGlobalState globalState(nullptr, &contourHead SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr)); SkOpEdgeBuilder builder(*path, &contourHead, &allocator, &globalState); builder.finish(&allocator); if (!contourHead.next()) { return false; } contourHead.resetReverse(); bool writePath = false; SkOpSpan* topSpan; globalState.setPhase(SkOpGlobalState::kFixWinding); while ((topSpan = FindSortableTop(&contourHead))) { SkOpSegment* topSegment = topSpan->segment(); SkOpContour* topContour = topSegment->contour(); SkASSERT(topContour->isCcw() >= 0); #if DEBUG_WINDING SkDebugf("%s id=%d nested=%d ccw=%d\n", __FUNCTION__, topSegment->debugID(), globalState.nested(), topContour->isCcw()); #endif if ((globalState.nested() & 1) != SkToBool(topContour->isCcw())) { topContour->setReverse(); writePath = true; } topContour->markDone(); globalState.clearNested(); } if (!writePath) { path->setFillType(fillType); return true; } SkPath empty; SkPathWriter woundPath(empty); SkOpContour* test = &contourHead; do { if (test->reversed()) { test->toReversePath(&woundPath); } else { test->toPath(&woundPath); } } while ((test = test->next())); *path = *woundPath.nativePath(); path->setFillType(fillType); return true; }
static int contourRangeCheckY(const SkTArray<SkOpContour*, true>& contourList, SkOpSegment** currentPtr, int* indexPtr, int* endIndexPtr, double* bestHit, SkScalar* bestDx, bool* tryAgain, double* midPtr, bool opp) { const int index = *indexPtr; const int endIndex = *endIndexPtr; const double mid = *midPtr; const SkOpSegment* current = *currentPtr; double tAtMid = current->tAtMid(index, endIndex, mid); SkPoint basePt = current->ptAtT(tAtMid); int contourCount = contourList.count(); SkScalar bestY = SK_ScalarMin; SkOpSegment* bestSeg = NULL; int bestTIndex = 0; bool bestOpp; bool hitSomething = false; for (int cTest = 0; cTest < contourCount; ++cTest) { SkOpContour* contour = contourList[cTest]; bool testOpp = contour->operand() ^ current->operand() ^ opp; if (basePt.fY < contour->bounds().fTop) { continue; } if (bestY > contour->bounds().fBottom) { continue; } int segmentCount = contour->segments().count(); for (int test = 0; test < segmentCount; ++test) { SkOpSegment* testSeg = &contour->segments()[test]; SkScalar testY = bestY; double testHit; int testTIndex = testSeg->crossedSpanY(basePt, &testY, &testHit, &hitSomething, tAtMid, testOpp, testSeg == current); if (testTIndex < 0) { if (testTIndex == SK_MinS32) { hitSomething = true; bestSeg = NULL; goto abortContours; // vertical encountered, return and try different point } continue; } if (testSeg == current && current->betweenTs(index, testHit, endIndex)) { double baseT = current->t(index); double endT = current->t(endIndex); double newMid = (testHit - baseT) / (endT - baseT); #if DEBUG_WINDING double midT = current->tAtMid(index, endIndex, mid); SkPoint midXY = current->xyAtT(midT); double newMidT = current->tAtMid(index, endIndex, newMid); SkPoint newXY = current->xyAtT(newMidT); SkDebugf("%s [%d] mid=%1.9g->%1.9g s=%1.9g (%1.9g,%1.9g) m=%1.9g (%1.9g,%1.9g)" " n=%1.9g (%1.9g,%1.9g) e=%1.9g (%1.9g,%1.9g)\n", __FUNCTION__, current->debugID(), mid, newMid, baseT, current->xAtT(index), current->yAtT(index), baseT + mid * (endT - baseT), midXY.fX, midXY.fY, baseT + newMid * (endT - baseT), newXY.fX, newXY.fY, endT, current->xAtT(endIndex), current->yAtT(endIndex)); #endif *midPtr = newMid * 2; // calling loop with divide by 2 before continuing return SK_MinS32; } bestSeg = testSeg; *bestHit = testHit; bestOpp = testOpp; bestTIndex = testTIndex; bestY = testY; } } abortContours: int result; if (!bestSeg) { result = hitSomething ? SK_MinS32 : 0; } else { if (bestSeg->windSum(bestTIndex) == SK_MinS32) { *currentPtr = bestSeg; *indexPtr = bestTIndex; *endIndexPtr = bestSeg->nextSpan(bestTIndex, 1); SkASSERT(*indexPtr != *endIndexPtr && *indexPtr >= 0 && *endIndexPtr >= 0); *tryAgain = true; return 0; } result = bestSeg->windingAtT(*bestHit, bestTIndex, bestOpp, bestDx); SkASSERT(result == SK_MinS32 || *bestDx); } double baseT = current->t(index); double endT = current->t(endIndex); *bestHit = baseT + mid * (endT - baseT); return result; }
bool Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) { #if DEBUG_SHOW_TEST_NAME char* debugName = DEBUG_FILENAME_STRING; if (debugName && debugName[0]) { SkPathOpsDebug::BumpTestName(debugName); SkPathOpsDebug::ShowPath(one, two, op, debugName); } #endif op = gOpInverse[op][one.isInverseFillType()][two.isInverseFillType()]; SkPath::FillType fillType = gOutInverse[op][one.isInverseFillType()][two.isInverseFillType()] ? SkPath::kInverseEvenOdd_FillType : SkPath::kEvenOdd_FillType; const SkPath* minuend = &one; const SkPath* subtrahend = &two; if (op == kReverseDifference_PathOp) { minuend = &two; subtrahend = &one; op = kDifference_PathOp; } #if DEBUG_SORT || DEBUG_SWAP_TOP SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault; #endif // turn path into list of segments SkTArray<SkOpContour> contours; // FIXME: add self-intersecting cubics' T values to segment SkOpEdgeBuilder builder(*minuend, contours); const int xorMask = builder.xorMask(); builder.addOperand(*subtrahend); if (!builder.finish()) { return false; } result->reset(); result->setFillType(fillType); const int xorOpMask = builder.xorMask(); SkTArray<SkOpContour*, true> contourList; MakeContourList(contours, contourList, xorMask == kEvenOdd_PathOpsMask, xorOpMask == kEvenOdd_PathOpsMask); SkOpContour** currentPtr = contourList.begin(); if (!currentPtr) { return true; } SkOpContour** listEnd = contourList.end(); // find all intersections between segments do { SkOpContour** nextPtr = currentPtr; SkOpContour* current = *currentPtr++; if (current->containsCubics()) { AddSelfIntersectTs(current); } SkOpContour* next; do { next = *nextPtr++; } while (AddIntersectTs(current, next) && nextPtr != listEnd); } while (currentPtr != listEnd); // eat through coincident edges int total = 0; int index; for (index = 0; index < contourList.count(); ++index) { total += contourList[index]->segments().count(); } HandleCoincidence(&contourList, total); // construct closed contours SkPathWriter wrapper(*result); bridgeOp(contourList, op, xorMask, xorOpMask, &wrapper); { // if some edges could not be resolved, assemble remaining fragments SkPath temp; temp.setFillType(fillType); SkPathWriter assembled(temp); Assemble(wrapper, &assembled); *result = *assembled.nativePath(); result->setFillType(fillType); } return true; }
static void testQuadAngles(skiatest::Reporter* reporter, const SkDQuad& quad1, const SkDQuad& quad2, int testNo, SkChunkAlloc* allocator) { SkPoint shortQuads[2][3]; SkOpContour contour; SkOpGlobalState state(NULL PATH_OPS_DEBUG_PARAMS(&contour)); contour.init(&state, false, false); makeSegment(&contour, quad1, shortQuads[0], allocator); makeSegment(&contour, quad1, shortQuads[1], allocator); SkOpSegment* seg1 = contour.first(); seg1->debugAddAngle(0, 1, allocator); SkOpSegment* seg2 = seg1->next(); seg2->debugAddAngle(0, 1, allocator); int realOverlap = PathOpsAngleTester::ConvexHullOverlaps(*seg1->debugLastAngle(), *seg2->debugLastAngle()); const SkDPoint& origin = quad1[0]; REPORTER_ASSERT(reporter, origin == quad2[0]); double a1s = atan2(origin.fY - quad1[1].fY, quad1[1].fX - origin.fX); double a1e = atan2(origin.fY - quad1[2].fY, quad1[2].fX - origin.fX); double a2s = atan2(origin.fY - quad2[1].fY, quad2[1].fX - origin.fX); double a2e = atan2(origin.fY - quad2[2].fY, quad2[2].fX - origin.fX); bool oldSchoolOverlap = radianBetween(a1s, a2s, a1e) || radianBetween(a1s, a2e, a1e) || radianBetween(a2s, a1s, a2e) || radianBetween(a2s, a1e, a2e); int overlap = quadHullsOverlap(reporter, quad1, quad2); bool realMatchesOverlap = realOverlap == overlap || SK_ScalarPI - fabs(a2s - a1s) < 0.002; if (realOverlap != overlap) { SkDebugf("\nSK_ScalarPI - fabs(a2s - a1s) = %1.9g\n", SK_ScalarPI - fabs(a2s - a1s)); } if (!realMatchesOverlap) { DumpQ(quad1, quad2, testNo); } REPORTER_ASSERT(reporter, realMatchesOverlap); if (oldSchoolOverlap != (overlap < 0)) { overlap = quadHullsOverlap(reporter, quad1, quad2); // set a breakpoint and debug if assert fires REPORTER_ASSERT(reporter, oldSchoolOverlap == (overlap < 0)); } SkDVector v1s = quad1[1] - quad1[0]; SkDVector v1e = quad1[2] - quad1[0]; SkDVector v2s = quad2[1] - quad2[0]; SkDVector v2e = quad2[2] - quad2[0]; double vDir[2] = { v1s.cross(v1e), v2s.cross(v2e) }; bool ray1In2 = v1s.cross(v2s) * vDir[1] <= 0 && v1s.cross(v2e) * vDir[1] >= 0; bool ray2In1 = v2s.cross(v1s) * vDir[0] <= 0 && v2s.cross(v1e) * vDir[0] >= 0; if (overlap >= 0) { // verify that hulls really don't overlap REPORTER_ASSERT(reporter, !ray1In2); REPORTER_ASSERT(reporter, !ray2In1); bool ctrl1In2 = v1e.cross(v2s) * vDir[1] <= 0 && v1e.cross(v2e) * vDir[1] >= 0; REPORTER_ASSERT(reporter, !ctrl1In2); bool ctrl2In1 = v2e.cross(v1s) * vDir[0] <= 0 && v2e.cross(v1e) * vDir[0] >= 0; REPORTER_ASSERT(reporter, !ctrl2In1); // check answer against reference bruteForce(reporter, quad1, quad2, overlap > 0); } // continue end point rays and see if they intersect the opposite curve SkDLine rays[] = {{{origin, quad2[2]}}, {{origin, quad1[2]}}}; const SkDQuad* quads[] = {&quad1, &quad2}; SkDVector midSpokes[2]; SkIntersections intersect[2]; double minX, minY, maxX, maxY; minX = minY = SK_ScalarInfinity; maxX = maxY = -SK_ScalarInfinity; double maxWidth = 0; bool useIntersect = false; double smallestTs[] = {1, 1}; for (unsigned index = 0; index < SK_ARRAY_COUNT(quads); ++index) { const SkDQuad& q = *quads[index]; midSpokes[index] = q.ptAtT(0.5) - origin; minX = SkTMin(SkTMin(SkTMin(minX, origin.fX), q[1].fX), q[2].fX); minY = SkTMin(SkTMin(SkTMin(minY, origin.fY), q[1].fY), q[2].fY); maxX = SkTMax(SkTMax(SkTMax(maxX, origin.fX), q[1].fX), q[2].fX); maxY = SkTMax(SkTMax(SkTMax(maxY, origin.fY), q[1].fY), q[2].fY); maxWidth = SkTMax(maxWidth, SkTMax(maxX - minX, maxY - minY)); intersect[index].intersectRay(q, rays[index]); const SkIntersections& i = intersect[index]; REPORTER_ASSERT(reporter, i.used() >= 1); bool foundZero = false; double smallT = 1; for (int idx2 = 0; idx2 < i.used(); ++idx2) { double t = i[0][idx2]; if (t == 0) { foundZero = true; continue; } if (smallT > t) { smallT = t; } } REPORTER_ASSERT(reporter, foundZero == true); if (smallT == 1) { continue; } SkDVector ray = q.ptAtT(smallT) - origin; SkDVector end = rays[index][1] - origin; if (ray.fX * end.fX < 0 || ray.fY * end.fY < 0) { continue; } double rayDist = ray.length(); double endDist = end.length(); double delta = fabs(rayDist - endDist) / maxWidth; if (delta > 1e-4) { useIntersect ^= true; } smallestTs[index] = smallT; } bool firstInside; if (useIntersect) { int sIndex = (int) (smallestTs[1] < 1); REPORTER_ASSERT(reporter, smallestTs[sIndex ^ 1] == 1); double t = smallestTs[sIndex]; const SkDQuad& q = *quads[sIndex]; SkDVector ray = q.ptAtT(t) - origin; SkDVector end = rays[sIndex][1] - origin; double rayDist = ray.length(); double endDist = end.length(); SkDVector mid = q.ptAtT(t / 2) - origin; double midXray = mid.crossCheck(ray); if (gPathOpsAngleIdeasVerbose) { SkDebugf("rayDist>endDist:%d sIndex==0:%d vDir[sIndex]<0:%d midXray<0:%d\n", rayDist > endDist, sIndex == 0, vDir[sIndex] < 0, midXray < 0); } SkASSERT(SkScalarSignAsInt(SkDoubleToScalar(midXray)) == SkScalarSignAsInt(SkDoubleToScalar(vDir[sIndex]))); firstInside = (rayDist > endDist) ^ (sIndex == 0) ^ (vDir[sIndex] < 0); } else if (overlap >= 0) { return; // answer has already been determined } else { firstInside = checkParallel(reporter, quad1, quad2); } if (overlap < 0) { SkDEBUGCODE(int realEnds =) PathOpsAngleTester::EndsIntersect(*seg1->debugLastAngle(), *seg2->debugLastAngle()); SkASSERT(realEnds == (firstInside ? 1 : 0)); }