// The objective here is to inset all of the edges by the given distance, and then // remove any invalid inset edges by detecting right-hand turns. In a ccw polygon, // we should only be making left-hand turns (for cw polygons, we use the winding // parameter to reverse this). We detect this by checking whether the second intersection // on an edge is closer to its tail than the first one. // // We might also have the case that there is no intersection between two neighboring inset edges. // In this case, one edge will lie to the right of the other and should be discarded along with // its previous intersection (if any). // // Note: the assumption is that inputPolygon is convex and has no coincident points. // bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize, std::function<SkScalar(int index)> insetDistanceFunc, SkTDArray<SkPoint>* insetPolygon) { if (inputPolygonSize < 3) { return false; } int winding = get_winding(inputPolygonVerts, inputPolygonSize); if (0 == winding) { return false; } // set up struct EdgeData { InsetSegment fInset; SkPoint fIntersection; SkScalar fTValue; bool fValid; }; SkAutoSTMalloc<64, EdgeData> edgeData(inputPolygonSize); for (int i = 0; i < inputPolygonSize; ++i) { int j = (i + 1) % inputPolygonSize; SkOffsetSegment(inputPolygonVerts[i], inputPolygonVerts[j], insetDistanceFunc(i), insetDistanceFunc(j), winding, &edgeData[i].fInset.fP0, &edgeData[i].fInset.fP1); edgeData[i].fIntersection = edgeData[i].fInset.fP0; edgeData[i].fTValue = SK_ScalarMin; edgeData[i].fValid = true; } int prevIndex = inputPolygonSize - 1; int currIndex = 0; int insetVertexCount = inputPolygonSize; while (prevIndex != currIndex) { if (!edgeData[prevIndex].fValid) { prevIndex = (prevIndex + inputPolygonSize - 1) % inputPolygonSize; continue; } SkScalar s, t; SkPoint intersection; if (compute_intersection(edgeData[prevIndex].fInset, edgeData[currIndex].fInset, &intersection, &s, &t)) { // if new intersection is further back on previous inset from the prior intersection if (s < edgeData[prevIndex].fTValue) { // no point in considering this one again edgeData[prevIndex].fValid = false; --insetVertexCount; // go back one segment prevIndex = (prevIndex + inputPolygonSize - 1) % inputPolygonSize; // we've already considered this intersection, we're done } else if (edgeData[currIndex].fTValue > SK_ScalarMin && intersection.equalsWithinTolerance(edgeData[currIndex].fIntersection, 1.0e-6f)) { break; } else { // add intersection edgeData[currIndex].fIntersection = intersection; edgeData[currIndex].fTValue = t; // go to next segment prevIndex = currIndex; currIndex = (currIndex + 1) % inputPolygonSize; } } else { // if prev to right side of curr int side = winding*compute_side(edgeData[currIndex].fInset.fP0, edgeData[currIndex].fInset.fP1, edgeData[prevIndex].fInset.fP1); if (side < 0 && side == winding*compute_side(edgeData[currIndex].fInset.fP0, edgeData[currIndex].fInset.fP1, edgeData[prevIndex].fInset.fP0)) { // no point in considering this one again edgeData[prevIndex].fValid = false; --insetVertexCount; // go back one segment prevIndex = (prevIndex + inputPolygonSize - 1) % inputPolygonSize; } else { // move to next segment edgeData[currIndex].fValid = false; --insetVertexCount; currIndex = (currIndex + 1) % inputPolygonSize; } } } // store all the valid intersections that aren't nearly coincident // TODO: look at the main algorithm and see if we can detect these better static constexpr SkScalar kCleanupTolerance = 0.01f; insetPolygon->reset(); insetPolygon->setReserve(insetVertexCount); currIndex = -1; for (int i = 0; i < inputPolygonSize; ++i) { if (edgeData[i].fValid && (currIndex == -1 || !edgeData[i].fIntersection.equalsWithinTolerance((*insetPolygon)[currIndex], kCleanupTolerance))) { *insetPolygon->push() = edgeData[i].fIntersection; currIndex++; } } // make sure the first and last points aren't coincident if (currIndex >= 1 && (*insetPolygon)[0].equalsWithinTolerance((*insetPolygon)[currIndex], kCleanupTolerance)) { insetPolygon->pop(); } SkASSERT(is_convex(*insetPolygon)); return (insetPolygon->count() >= 3); }