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()); }
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)); }
bool SkOpEdgeBuilder::walk() { uint8_t* verbPtr = fPathVerbs.begin(); uint8_t* endOfFirstHalf = &verbPtr[fSecondHalf]; SkPoint* pointsPtr = fPathPts.begin() - 1; SkScalar* weightPtr = fWeights.begin(); SkPath::Verb verb; SkOpContour* contour = fContourBuilder.contour(); while ((verb = (SkPath::Verb) *verbPtr) != SkPath::kDone_Verb) { if (verbPtr == endOfFirstHalf) { fOperand = true; } verbPtr++; switch (verb) { case SkPath::kMove_Verb: if (contour && contour->count()) { if (fAllowOpenContours) { complete(); } else if (!close()) { return false; } } if (!contour) { fContourBuilder.setContour(contour = fContoursHead->appendContour()); } contour->init(fGlobalState, fOperand, fXorMask[fOperand] == kEvenOdd_PathOpsMask); pointsPtr += 1; continue; case SkPath::kLine_Verb: fContourBuilder.addLine(pointsPtr); break; case SkPath::kQuad_Verb: { SkVector v1 = pointsPtr[1] - pointsPtr[0]; SkVector v2 = pointsPtr[2] - pointsPtr[1]; if (v1.dot(v2) < 0) { SkPoint pair[5]; if (SkChopQuadAtMaxCurvature(pointsPtr, pair) == 1) { goto addOneQuad; } if (!SkScalarsAreFinite(&pair[0].fX, SK_ARRAY_COUNT(pair) * 2)) { return false; } for (unsigned index = 0; index < SK_ARRAY_COUNT(pair); ++index) { force_small_to_zero(&pair[index]); } SkPoint cStorage[2][2]; SkPath::Verb v1 = SkReduceOrder::Quad(&pair[0], cStorage[0]); SkPath::Verb v2 = SkReduceOrder::Quad(&pair[2], cStorage[1]); SkPoint* curve1 = v1 != SkPath::kLine_Verb ? &pair[0] : cStorage[0]; SkPoint* curve2 = v2 != SkPath::kLine_Verb ? &pair[2] : cStorage[1]; if (can_add_curve(v1, curve1) && can_add_curve(v2, curve2)) { fContourBuilder.addCurve(v1, curve1); fContourBuilder.addCurve(v2, curve2); break; } } } addOneQuad: fContourBuilder.addQuad(pointsPtr); break; case SkPath::kConic_Verb: { SkVector v1 = pointsPtr[1] - pointsPtr[0]; SkVector v2 = pointsPtr[2] - pointsPtr[1]; SkScalar weight = *weightPtr++; if (v1.dot(v2) < 0) { // FIXME: max curvature for conics hasn't been implemented; use placeholder SkScalar maxCurvature = SkFindQuadMaxCurvature(pointsPtr); if (maxCurvature > 0) { SkConic conic(pointsPtr, weight); SkConic pair[2]; if (!conic.chopAt(maxCurvature, pair)) { // if result can't be computed, use original fContourBuilder.addConic(pointsPtr, weight); break; } SkPoint cStorage[2][3]; SkPath::Verb v1 = SkReduceOrder::Conic(pair[0], cStorage[0]); SkPath::Verb v2 = SkReduceOrder::Conic(pair[1], cStorage[1]); SkPoint* curve1 = v1 != SkPath::kLine_Verb ? pair[0].fPts : cStorage[0]; SkPoint* curve2 = v2 != SkPath::kLine_Verb ? pair[1].fPts : cStorage[1]; if (can_add_curve(v1, curve1) && can_add_curve(v2, curve2)) { fContourBuilder.addCurve(v1, curve1, pair[0].fW); fContourBuilder.addCurve(v2, curve2, pair[1].fW); break; } } } fContourBuilder.addConic(pointsPtr, weight); } break; case SkPath::kCubic_Verb: { // Split complex cubics (such as self-intersecting curves or // ones with difficult curvature) in two before proceeding. // This can be required for intersection to succeed. SkScalar splitT[3]; int breaks = SkDCubic::ComplexBreak(pointsPtr, splitT); if (!breaks) { fContourBuilder.addCubic(pointsPtr); break; } SkASSERT(breaks <= (int) SK_ARRAY_COUNT(splitT)); struct Splitsville { double fT[2]; SkPoint fPts[4]; SkPoint fReduced[4]; SkPath::Verb fVerb; bool fCanAdd; } splits[4]; SkASSERT(SK_ARRAY_COUNT(splits) == SK_ARRAY_COUNT(splitT) + 1); SkTQSort(splitT, &splitT[breaks - 1]); for (int index = 0; index <= breaks; ++index) { Splitsville* split = &splits[index]; split->fT[0] = index ? splitT[index - 1] : 0; split->fT[1] = index < breaks ? splitT[index] : 1; SkDCubic part = SkDCubic::SubDivide(pointsPtr, split->fT[0], split->fT[1]); if (!part.toFloatPoints(split->fPts)) { return false; } split->fVerb = SkReduceOrder::Cubic(split->fPts, split->fReduced); SkPoint* curve = SkPath::kCubic_Verb == verb ? split->fPts : split->fReduced; split->fCanAdd = can_add_curve(split->fVerb, curve); } for (int index = 0; index <= breaks; ++index) { Splitsville* split = &splits[index]; if (!split->fCanAdd) { continue; } int prior = index; while (prior > 0 && !splits[prior - 1].fCanAdd) { --prior; } if (prior < index) { split->fT[0] = splits[prior].fT[0]; split->fPts[0] = splits[prior].fPts[0]; } int next = index; int breakLimit = SkTMin(breaks, (int) SK_ARRAY_COUNT(splits) - 1); while (next < breakLimit && !splits[next + 1].fCanAdd) { ++next; } if (next > index) { split->fT[1] = splits[next].fT[1]; split->fPts[3] = splits[next].fPts[3]; } if (prior < index || next > index) { split->fVerb = SkReduceOrder::Cubic(split->fPts, split->fReduced); } SkPoint* curve = SkPath::kCubic_Verb == split->fVerb ? split->fPts : split->fReduced; if (!can_add_curve(split->fVerb, curve)) { return false; } fContourBuilder.addCurve(split->fVerb, curve); } } break; case SkPath::kClose_Verb: SkASSERT(contour); if (!close()) { return false; } contour = nullptr; continue; default: SkDEBUGFAIL("bad verb"); return false; } SkASSERT(contour); if (contour->count()) { contour->debugValidate(); } pointsPtr += SkPathOpsVerbToPoints(verb); } fContourBuilder.flush(); if (contour && contour->count() &&!fAllowOpenContours && !close()) { return false; } return true; }