Index: src/pathops/SkOpAngle.cpp |
=================================================================== |
--- src/pathops/SkOpAngle.cpp (revision 0) |
+++ src/pathops/SkOpAngle.cpp (revision 0) |
@@ -0,0 +1,256 @@ |
+/* |
+ * Copyright 2012 Google Inc. |
+ * |
+ * Use of this source code is governed by a BSD-style license that can be |
+ * found in the LICENSE file. |
+ */ |
+#include "SkIntersections.h" |
+#include "SkOpAngle.h" |
+#include "SkPathOpsCurve.h" |
+ |
+// FIXME: this is bogus for quads and cubics |
+// if the quads and cubics' line from end pt to ctrl pt are coincident, |
+// there's no obvious way to determine the curve ordering from the |
+// derivatives alone. In particular, if one quadratic's coincident tangent |
+// is longer than the other curve, the final control point can place the |
+// longer curve on either side of the shorter one. |
+// Using Bezier curve focus http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf |
+// may provide some help, but nothing has been figured out yet. |
+ |
+/*( |
+for quads and cubics, set up a parameterized line (e.g. LineParameters ) |
+for points [0] to [1]. See if point [2] is on that line, or on one side |
+or the other. If it both quads' end points are on the same side, choose |
+the shorter tangent. If the tangents are equal, choose the better second |
+tangent angle |
+ |
+maybe I could set up LineParameters lazily |
+*/ |
+bool SkOpAngle::operator<(const SkOpAngle& rh) const { |
+ double y = dy(); |
+ double ry = rh.dy(); |
+ if ((y < 0) ^ (ry < 0)) { // OPTIMIZATION: better to use y * ry < 0 ? |
+ return y < 0; |
+ } |
+ double x = dx(); |
+ double rx = rh.dx(); |
+ if (y == 0 && ry == 0 && x * rx < 0) { |
+ return x < rx; |
+ } |
+ double x_ry = x * ry; |
+ double rx_y = rx * y; |
+ double cmp = x_ry - rx_y; |
+ if (!approximately_zero(cmp)) { |
+ return cmp < 0; |
+ } |
+ if (approximately_zero(x_ry) && approximately_zero(rx_y) |
+ && !approximately_zero_squared(cmp)) { |
+ return cmp < 0; |
+ } |
+ // at this point, the initial tangent line is coincident |
+ // see if edges curl away from each other |
+ if (fSide * rh.fSide <= 0 && (!approximately_zero(fSide) |
+ || !approximately_zero(rh.fSide))) { |
+ // FIXME: running demo will trigger this assertion |
+ // (don't know if commenting out will trigger further assertion or not) |
+ // commenting it out allows demo to run in release, though |
+ // SkASSERT(fSide != rh.fSide); |
+ return fSide < rh.fSide; |
+ } |
+ // see if either curve can be lengthened and try the tangent compare again |
+ if (cmp && (*fSpans)[fEnd].fOther != rh.fSegment // tangents not absolutely identical |
+ && (*rh.fSpans)[rh.fEnd].fOther != fSegment) { // and not intersecting |
+ SkOpAngle longer = *this; |
+ SkOpAngle rhLonger = rh; |
+ if (longer.lengthen() | rhLonger.lengthen()) { |
+ return longer < rhLonger; |
+ } |
+ } |
+ if ((fVerb == SkPath::kLine_Verb && approximately_zero(x) && approximately_zero(y)) |
+ || (rh.fVerb == SkPath::kLine_Verb |
+ && approximately_zero(rx) && approximately_zero(ry))) { |
+ // See general unsortable comment below. This case can happen when |
+ // one line has a non-zero change in t but no change in x and y. |
+ fUnsortable = true; |
+ rh.fUnsortable = true; |
+ return this < &rh; // even with no solution, return a stable sort |
+ } |
+ if ((*rh.fSpans)[SkMin32(rh.fStart, rh.fEnd)].fTiny |
+ || (*fSpans)[SkMin32(fStart, fEnd)].fTiny) { |
+ fUnsortable = true; |
+ rh.fUnsortable = true; |
+ return this < &rh; // even with no solution, return a stable sort |
+ } |
+ SkASSERT(fVerb >= SkPath::kQuad_Verb); |
+ SkASSERT(rh.fVerb >= SkPath::kQuad_Verb); |
+ // FIXME: until I can think of something better, project a ray from the |
+ // end of the shorter tangent to midway between the end points |
+ // through both curves and use the resulting angle to sort |
+ // FIXME: some of this setup can be moved to set() if it works, or cached if it's expensive |
+ double len = fTangent1.normalSquared(); |
+ double rlen = rh.fTangent1.normalSquared(); |
+ SkDLine ray; |
+ SkIntersections i, ri; |
+ int roots, rroots; |
+ bool flip = false; |
+ do { |
+ bool useThis = (len < rlen) ^ flip; |
+ const SkDCubic& part = useThis ? fCurvePart : rh.fCurvePart; |
+ SkPath::Verb partVerb = useThis ? fVerb : rh.fVerb; |
+ ray[0] = partVerb == SkPath::kCubic_Verb && part[0].approximatelyEqual(part[1]) ? |
+ part[2] : part[1]; |
+ ray[1].fX = (part[0].fX + part[partVerb].fX) / 2; |
+ ray[1].fY = (part[0].fY + part[partVerb].fY) / 2; |
+ SkASSERT(ray[0] != ray[1]); |
+ roots = (i.*CurveRay[fVerb])(fPts, ray); |
+ rroots = (ri.*CurveRay[rh.fVerb])(rh.fPts, ray); |
+ } while ((roots == 0 || rroots == 0) && (flip ^= true)); |
+ if (roots == 0 || rroots == 0) { |
+ // FIXME: we don't have a solution in this case. The interim solution |
+ // is to mark the edges as unsortable, exclude them from this and |
+ // future computations, and allow the returned path to be fragmented |
+ fUnsortable = true; |
+ rh.fUnsortable = true; |
+ return this < &rh; // even with no solution, return a stable sort |
+ } |
+ SkDPoint loc; |
+ double best = SK_ScalarInfinity; |
+ SkDVector dxy; |
+ double dist; |
+ int index; |
+ for (index = 0; index < roots; ++index) { |
+ loc = (*CurveDPointAtT[fVerb])(fPts, i[0][index]); |
+ dxy = loc - ray[0]; |
+ dist = dxy.lengthSquared(); |
+ if (best > dist) { |
+ best = dist; |
+ } |
+ } |
+ for (index = 0; index < rroots; ++index) { |
+ loc = (*CurveDPointAtT[rh.fVerb])(rh.fPts, ri[0][index]); |
+ dxy = loc - ray[0]; |
+ dist = dxy.lengthSquared(); |
+ if (best > dist) { |
+ return fSide < 0; |
+ } |
+ } |
+ return fSide > 0; |
+} |
+ |
+bool SkOpAngle::lengthen() { |
+ int newEnd = fEnd; |
+ if (fStart < fEnd ? ++newEnd < fSpans->count() : --newEnd >= 0) { |
+ fEnd = newEnd; |
+ setSpans(); |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+bool SkOpAngle::reverseLengthen() { |
+ if (fReversed) { |
+ return false; |
+ } |
+ int newEnd = fStart; |
+ if (fStart > fEnd ? ++newEnd < fSpans->count() : --newEnd >= 0) { |
+ fEnd = newEnd; |
+ fReversed = true; |
+ setSpans(); |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+void SkOpAngle::set(const SkPoint* orig, SkPath::Verb verb, const SkOpSegment* segment, |
+ int start, int end, const SkTDArray<SkOpSpan>& spans) { |
+ fSegment = segment; |
+ fStart = start; |
+ fEnd = end; |
+ fPts = orig; |
+ fVerb = verb; |
+ fSpans = &spans; |
+ fReversed = false; |
+ fUnsortable = false; |
+ setSpans(); |
+} |
+ |
+ |
+void SkOpAngle::setSpans() { |
+ double startT = (*fSpans)[fStart].fT; |
+ double endT = (*fSpans)[fEnd].fT; |
+ switch (fVerb) { |
+ case SkPath::kLine_Verb: { |
+ SkDLine l = SkDLine::SubDivide(fPts, startT, endT); |
+ // OPTIMIZATION: for pure line compares, we never need fTangent1.c |
+ fTangent1.lineEndPoints(l); |
+ fSide = 0; |
+ } break; |
+ case SkPath::kQuad_Verb: { |
+ SkDQuad& quad = (SkDQuad&)fCurvePart; |
+ quad = SkDQuad::SubDivide(fPts, startT, endT); |
+ fTangent1.quadEndPoints(quad, 0, 1); |
+ if (dx() == 0 && dy() == 0) { |
+ fTangent1.quadEndPoints(quad); |
+ } |
+ fSide = -fTangent1.pointDistance(fCurvePart[2]); // not normalized -- compare sign only |
+ } break; |
+ case SkPath::kCubic_Verb: { |
+ int nextC = 2; |
+ fCurvePart = SkDCubic::SubDivide(fPts, startT, endT); |
+ fTangent1.cubicEndPoints(fCurvePart, 0, 1); |
+ if (dx() == 0 && dy() == 0) { |
+ fTangent1.cubicEndPoints(fCurvePart, 0, 2); |
+ nextC = 3; |
+ if (dx() == 0 && dy() == 0) { |
+ fTangent1.cubicEndPoints(fCurvePart, 0, 3); |
+ } |
+ } |
+ fSide = -fTangent1.pointDistance(fCurvePart[nextC]); // compare sign only |
+ if (nextC == 2 && approximately_zero(fSide)) { |
+ fSide = -fTangent1.pointDistance(fCurvePart[3]); |
+ } |
+ } break; |
+ default: |
+ SkASSERT(0); |
+ } |
+ fUnsortable = dx() == 0 && dy() == 0; |
+ if (fUnsortable) { |
+ return; |
+ } |
+ SkASSERT(fStart != fEnd); |
+ int step = fStart < fEnd ? 1 : -1; // OPTIMIZE: worth fStart - fEnd >> 31 type macro? |
+ for (int index = fStart; index != fEnd; index += step) { |
+#if 1 |
+ const SkOpSpan& thisSpan = (*fSpans)[index]; |
+ const SkOpSpan& nextSpan = (*fSpans)[index + step]; |
+ if (thisSpan.fTiny || precisely_equal(thisSpan.fT, nextSpan.fT)) { |
+ continue; |
+ } |
+ fUnsortable = step > 0 ? thisSpan.fUnsortableStart : nextSpan.fUnsortableEnd; |
+#if DEBUG_UNSORTABLE |
+ if (fUnsortable) { |
+ SkPoint iPt = (*CurvePointAtT[fVerb])(fPts, thisSpan.fT); |
+ SkPoint ePt = (*CurvePointAtT[fVerb])(fPts, nextSpan.fT); |
+ SkDebugf("%s unsortable [%d] (%1.9g,%1.9g) [%d] (%1.9g,%1.9g)\n", __FUNCTION__, |
+ index, iPt.fX, iPt.fY, fEnd, ePt.fX, ePt.fY); |
+ } |
+#endif |
+ return; |
+#else |
+ if ((*fSpans)[index].fUnsortableStart) { |
+ fUnsortable = true; |
+ return; |
+ } |
+#endif |
+ } |
+#if 1 |
+#if DEBUG_UNSORTABLE |
+ SkPoint iPt = (*CurvePointAtT[fVerb])(fPts, startT); |
+ SkPoint ePt = (*CurvePointAtT[fVerb])(fPts, endT); |
+ SkDebugf("%s all tiny unsortable [%d] (%1.9g,%1.9g) [%d] (%1.9g,%1.9g)\n", __FUNCTION__, |
+ fStart, iPt.fX, iPt.fY, fEnd, ePt.fX, ePt.fY); |
+#endif |
+ fUnsortable = true; |
+#endif |
+} |
+ |