blob: 0c87d3ba9e840939bea323d5a6f6ba456e89b0c3 [file] [log] [blame]
/*
* 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 "SkOpSegment.h"
#include "SkPathOpsCurve.h"
#include "SkTSort.h"
#if DEBUG_ANGLE
#include "SkString.h"
#endif
/* Angles are sorted counterclockwise. The smallest angle has a positive x and the smallest
positive y. The largest angle has a positive x and a zero y. */
#if DEBUG_ANGLE
static bool CompareResult(SkString* bugOut, int append, bool compare) {
SkDebugf("%s %c %d\n", bugOut->c_str(), compare ? 'T' : 'F', append);
return compare;
}
#define COMPARE_RESULT(append, compare) CompareResult(&bugOut, append, compare)
#else
#define COMPARE_RESULT(append, compare) compare
#endif
/* quarter angle values for sector
31 x > 0, y == 0 horizontal line (to the right)
0 x > 0, y == epsilon quad/cubic horizontal tangent eventually going +y
1 x > 0, y > 0, x > y nearer horizontal angle
2 x + e == y quad/cubic 45 going horiz
3 x > 0, y > 0, x == y 45 angle
4 x == y + e quad/cubic 45 going vert
5 x > 0, y > 0, x < y nearer vertical angle
6 x == epsilon, y > 0 quad/cubic vertical tangent eventually going +x
7 x == 0, y > 0 vertical line (to the top)
8 7 6
9 | 5
10 | 4
11 | 3
12 \ | / 2
13 | 1
14 | 0
15 --------------+------------- 31
16 | 30
17 | 29
18 / | \ 28
19 | 27
20 | 26
21 | 25
22 23 24
*/
// return true if lh < this < rh
bool SkOpAngle::after(const SkOpAngle* test) const {
const SkOpAngle& lh = *test;
const SkOpAngle& rh = *lh.fNext;
SkASSERT(&lh != &rh);
#if DEBUG_ANGLE
SkString bugOut;
bugOut.printf("%s [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g"
" < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g"
" < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g ", __FUNCTION__,
lh.fSegment->debugID(), lh.debugID(), lh.fSectorStart, lh.fSectorEnd,
lh.fSegment->t(lh.fStart), lh.fSegment->t(lh.fEnd),
fSegment->debugID(), debugID(), fSectorStart, fSectorEnd, fSegment->t(fStart),
fSegment->t(fEnd),
rh.fSegment->debugID(), rh.debugID(), rh.fSectorStart, rh.fSectorEnd,
rh.fSegment->t(rh.fStart), rh.fSegment->t(rh.fEnd));
#endif
if (lh.fComputeSector && !const_cast<SkOpAngle&>(lh).computeSector()) {
return COMPARE_RESULT(1, true);
}
if (fComputeSector && !const_cast<SkOpAngle*>(this)->computeSector()) {
return COMPARE_RESULT(2, true);
}
if (rh.fComputeSector && !const_cast<SkOpAngle&>(rh).computeSector()) {
return COMPARE_RESULT(3, true);
}
#if DEBUG_ANGLE // reset bugOut with computed sectors
bugOut.printf("%s [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g"
" < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g"
" < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g ", __FUNCTION__,
lh.fSegment->debugID(), lh.debugID(), lh.fSectorStart, lh.fSectorEnd,
lh.fSegment->t(lh.fStart), lh.fSegment->t(lh.fEnd),
fSegment->debugID(), debugID(), fSectorStart, fSectorEnd, fSegment->t(fStart),
fSegment->t(fEnd),
rh.fSegment->debugID(), rh.debugID(), rh.fSectorStart, rh.fSectorEnd,
rh.fSegment->t(rh.fStart), rh.fSegment->t(rh.fEnd));
#endif
bool ltrOverlap = (lh.fSectorMask | rh.fSectorMask) & fSectorMask;
bool lrOverlap = lh.fSectorMask & rh.fSectorMask;
int lrOrder; // set to -1 if either order works
if (!lrOverlap) { // no lh/rh sector overlap
if (!ltrOverlap) { // no lh/this/rh sector overlap
return COMPARE_RESULT(4, (lh.fSectorEnd > rh.fSectorStart)
^ (fSectorStart > lh.fSectorEnd) ^ (fSectorStart > rh.fSectorStart));
}
int lrGap = (rh.fSectorStart - lh.fSectorStart + 32) & 0x1f;
/* A tiny change can move the start +/- 4. The order can only be determined if
lr gap is not 12 to 20 or -12 to -20.
-31 ..-21 1
-20 ..-12 -1
-11 .. -1 0
0 shouldn't get here
11 .. 1 1
12 .. 20 -1
21 .. 31 0
*/
lrOrder = lrGap > 20 ? 0 : lrGap > 11 ? -1 : 1;
} else {
lrOrder = (int) lh.orderable(rh);
if (!ltrOverlap) {
return COMPARE_RESULT(5, !lrOrder);
}
}
int ltOrder;
SkASSERT((lh.fSectorMask & fSectorMask) || (rh.fSectorMask & fSectorMask));
if (lh.fSectorMask & fSectorMask) {
ltOrder = (int) lh.orderable(*this);
} else {
int ltGap = (fSectorStart - lh.fSectorStart + 32) & 0x1f;
ltOrder = ltGap > 20 ? 0 : ltGap > 11 ? -1 : 1;
}
int trOrder;
if (rh.fSectorMask & fSectorMask) {
trOrder = (int) orderable(rh);
} else {
int trGap = (rh.fSectorStart - fSectorStart + 32) & 0x1f;
trOrder = trGap > 20 ? 0 : trGap > 11 ? -1 : 1;
}
if (lrOrder >= 0 && ltOrder >= 0 && trOrder >= 0) {
return COMPARE_RESULT(7, lrOrder ? (ltOrder & trOrder) : (ltOrder | trOrder));
}
SkASSERT(lrOrder >= 0 || ltOrder >= 0 || trOrder >= 0);
// There's not enough information to sort. Get the pairs of angles in opposite planes.
// If an order is < 0, the pair is already in an opposite plane. Check the remaining pairs.
// FIXME : once all variants are understood, rewrite this more simply
if (ltOrder == 0 && lrOrder == 0) {
SkASSERT(trOrder < 0);
// FIXME : once this is verified to work, remove one opposite angle call
SkDEBUGCODE(bool lrOpposite = lh.oppositePlanes(rh));
bool ltOpposite = lh.oppositePlanes(*this);
SkASSERT(lrOpposite != ltOpposite);
return COMPARE_RESULT(8, ltOpposite);
} else if (ltOrder == 1 && trOrder == 0) {
SkASSERT(lrOrder < 0);
SkDEBUGCODE(bool ltOpposite = lh.oppositePlanes(*this));
bool trOpposite = oppositePlanes(rh);
SkASSERT(ltOpposite != trOpposite);
return COMPARE_RESULT(9, trOpposite);
} else if (lrOrder == 1 && trOrder == 1) {
SkASSERT(ltOrder < 0);
SkDEBUGCODE(bool trOpposite = oppositePlanes(rh));
bool lrOpposite = lh.oppositePlanes(rh);
SkASSERT(lrOpposite != trOpposite);
return COMPARE_RESULT(10, lrOpposite);
}
if (lrOrder < 0) {
if (ltOrder < 0) {
return COMPARE_RESULT(11, trOrder);
}
return COMPARE_RESULT(12, ltOrder);
}
return COMPARE_RESULT(13, !lrOrder);
}
// given a line, see if the opposite curve's convex hull is all on one side
// returns -1=not on one side 0=this CW of test 1=this CCW of test
int SkOpAngle::allOnOneSide(const SkOpAngle& test) const {
SkASSERT(!fIsCurve);
SkASSERT(test.fIsCurve);
const SkDPoint& origin = test.fCurvePart[0];
SkVector line;
if (fSegment->verb() == SkPath::kLine_Verb) {
const SkPoint* linePts = fSegment->pts();
int lineStart = fStart < fEnd ? 0 : 1;
line = linePts[lineStart ^ 1] - linePts[lineStart];
} else {
SkPoint shortPts[2] = { fCurvePart[0].asSkPoint(), fCurvePart[1].asSkPoint() };
line = shortPts[1] - shortPts[0];
}
float crosses[3];
SkPath::Verb testVerb = test.fSegment->verb();
int iMax = SkPathOpsVerbToPoints(testVerb);
// SkASSERT(origin == test.fCurveHalf[0]);
const SkDCubic& testCurve = test.fCurvePart;
// do {
for (int index = 1; index <= iMax; ++index) {
float xy1 = (float) (line.fX * (testCurve[index].fY - origin.fY));
float xy2 = (float) (line.fY * (testCurve[index].fX - origin.fX));
crosses[index - 1] = AlmostEqualUlps(xy1, xy2) ? 0 : xy1 - xy2;
}
if (crosses[0] * crosses[1] < 0) {
return -1;
}
if (SkPath::kCubic_Verb == testVerb) {
if (crosses[0] * crosses[2] < 0 || crosses[1] * crosses[2] < 0) {
return -1;
}
}
if (crosses[0]) {
return crosses[0] < 0;
}
if (crosses[1]) {
return crosses[1] < 0;
}
if (SkPath::kCubic_Verb == testVerb && crosses[2]) {
return crosses[2] < 0;
}
fUnorderable = true;
return -1;
}
bool SkOpAngle::calcSlop(double x, double y, double rx, double ry, bool* result) const {
double absX = fabs(x);
double absY = fabs(y);
double length = absX < absY ? absX / 2 + absY : absX + absY / 2;
int exponent;
(void) frexp(length, &exponent);
double epsilon = ldexp(FLT_EPSILON, exponent);
SkPath::Verb verb = fSegment->verb();
SkASSERT(verb == SkPath::kQuad_Verb || verb == SkPath::kCubic_Verb);
// FIXME: the quad and cubic factors are made up ; determine actual values
double slop = verb == SkPath::kQuad_Verb ? 4 * epsilon : 512 * epsilon;
double xSlop = slop;
double ySlop = x * y < 0 ? -xSlop : xSlop; // OPTIMIZATION: use copysign / _copysign ?
double x1 = x - xSlop;
double y1 = y + ySlop;
double x_ry1 = x1 * ry;
double rx_y1 = rx * y1;
*result = x_ry1 < rx_y1;
double x2 = x + xSlop;
double y2 = y - ySlop;
double x_ry2 = x2 * ry;
double rx_y2 = rx * y2;
bool less2 = x_ry2 < rx_y2;
return *result == less2;
}
bool SkOpAngle::checkCrossesZero() const {
int start = SkTMin(fSectorStart, fSectorEnd);
int end = SkTMax(fSectorStart, fSectorEnd);
bool crossesZero = end - start > 16;
return crossesZero;
}
bool SkOpAngle::checkParallel(const SkOpAngle& rh) const {
SkDVector scratch[2];
const SkDVector* sweep, * tweep;
if (!fUnorderedSweep) {
sweep = fSweep;
} else {
scratch[0] = fCurvePart[1] - fCurvePart[0];
sweep = &scratch[0];
}
if (!rh.fUnorderedSweep) {
tweep = rh.fSweep;
} else {
scratch[1] = rh.fCurvePart[1] - rh.fCurvePart[0];
tweep = &scratch[1];
}
double s0xt0 = sweep->crossCheck(*tweep);
if (tangentsDiverge(rh, s0xt0)) {
return s0xt0 < 0;
}
SkDVector m0 = fSegment->dPtAtT(midT()) - fCurvePart[0];
SkDVector m1 = rh.fSegment->dPtAtT(rh.midT()) - rh.fCurvePart[0];
double m0xm1 = m0.crossCheck(m1);
if (m0xm1 == 0) {
fUnorderable = true;
rh.fUnorderable = true;
return true;
}
return m0xm1 < 0;
}
// the original angle is too short to get meaningful sector information
// lengthen it until it is long enough to be meaningful or leave it unset if lengthening it
// would cause it to intersect one of the adjacent angles
bool SkOpAngle::computeSector() {
if (fComputedSector) {
return !fUnorderable;
}
// SkASSERT(fSegment->verb() != SkPath::kLine_Verb && small());
fComputedSector = true;
int step = fStart < fEnd ? 1 : -1;
int limit = step > 0 ? fSegment->count() : -1;
int checkEnd = fEnd;
do {
// advance end
const SkOpSpan& span = fSegment->span(checkEnd);
const SkOpSegment* other = span.fOther;
int oCount = other->count();
for (int oIndex = 0; oIndex < oCount; ++oIndex) {
const SkOpSpan& oSpan = other->span(oIndex);
if (oSpan.fOther != fSegment) {
continue;
}
if (oSpan.fOtherIndex == checkEnd) {
continue;
}
if (!approximately_equal(oSpan.fOtherT, span.fT)) {
continue;
}
goto recomputeSector;
}
checkEnd += step;
} while (checkEnd != limit);
recomputeSector:
if (checkEnd == fEnd || checkEnd - step == fEnd) {
fUnorderable = true;
return false;
}
int saveEnd = fEnd;
fComputedEnd = fEnd = checkEnd - step;
setSpans();
setSector();
fEnd = saveEnd;
return !fUnorderable;
}
// returns -1 if overlaps 0 if no overlap cw 1 if no overlap ccw
int SkOpAngle::convexHullOverlaps(const SkOpAngle& rh) const {
const SkDVector* sweep = fSweep;
const SkDVector* tweep = rh.fSweep;
double s0xs1 = sweep[0].crossCheck(sweep[1]);
double s0xt0 = sweep[0].crossCheck(tweep[0]);
double s1xt0 = sweep[1].crossCheck(tweep[0]);
bool tBetweenS = s0xs1 > 0 ? s0xt0 > 0 && s1xt0 < 0 : s0xt0 < 0 && s1xt0 > 0;
double s0xt1 = sweep[0].crossCheck(tweep[1]);
double s1xt1 = sweep[1].crossCheck(tweep[1]);
tBetweenS |= s0xs1 > 0 ? s0xt1 > 0 && s1xt1 < 0 : s0xt1 < 0 && s1xt1 > 0;
double t0xt1 = tweep[0].crossCheck(tweep[1]);
if (tBetweenS) {
return -1;
}
if ((s0xt0 == 0 && s1xt1 == 0) || (s1xt0 == 0 && s0xt1 == 0)) { // s0 to s1 equals t0 to t1
return -1;
}
bool sBetweenT = t0xt1 > 0 ? s0xt0 < 0 && s0xt1 > 0 : s0xt0 > 0 && s0xt1 < 0;
sBetweenT |= t0xt1 > 0 ? s1xt0 < 0 && s1xt1 > 0 : s1xt0 > 0 && s1xt1 < 0;
if (sBetweenT) {
return -1;
}
// if all of the sweeps are in the same half plane, then the order of any pair is enough
if (s0xt0 >= 0 && s0xt1 >= 0 && s1xt0 >= 0 && s1xt1 >= 0) {
return 0;
}
if (s0xt0 <= 0 && s0xt1 <= 0 && s1xt0 <= 0 && s1xt1 <= 0) {
return 1;
}
// if the outside sweeps are greater than 180 degress:
// first assume the inital tangents are the ordering
// if the midpoint direction matches the inital order, that is enough
SkDVector m0 = fSegment->dPtAtT(midT()) - fCurvePart[0];
SkDVector m1 = rh.fSegment->dPtAtT(rh.midT()) - rh.fCurvePart[0];
double m0xm1 = m0.crossCheck(m1);
if (s0xt0 > 0 && m0xm1 > 0) {
return 0;
}
if (s0xt0 < 0 && m0xm1 < 0) {
return 1;
}
if (tangentsDiverge(rh, s0xt0)) {
return s0xt0 < 0;
}
return m0xm1 < 0;
}
// OPTIMIZATION: longest can all be either lazily computed here or precomputed in setup
double SkOpAngle::distEndRatio(double dist) const {
double longest = 0;
const SkOpSegment& segment = *this->segment();
int ptCount = SkPathOpsVerbToPoints(segment.verb());
const SkPoint* pts = segment.pts();
for (int idx1 = 0; idx1 <= ptCount - 1; ++idx1) {
for (int idx2 = idx1 + 1; idx2 <= ptCount; ++idx2) {
if (idx1 == idx2) {
continue;
}
SkDVector v;
v.set(pts[idx2] - pts[idx1]);
double lenSq = v.lengthSquared();
longest = SkTMax(longest, lenSq);
}
}
return sqrt(longest) / dist;
}
bool SkOpAngle::endsIntersect(const SkOpAngle& rh) const {
SkPath::Verb lVerb = fSegment->verb();
SkPath::Verb rVerb = rh.fSegment->verb();
int lPts = SkPathOpsVerbToPoints(lVerb);
int rPts = SkPathOpsVerbToPoints(rVerb);
SkDLine rays[] = {{{fCurvePart[0], rh.fCurvePart[rPts]}},
{{fCurvePart[0], fCurvePart[lPts]}}};
if (rays[0][1] == rays[1][1]) {
return checkParallel(rh);
}
double smallTs[2] = {-1, -1};
bool limited[2] = {false, false};
for (int index = 0; index < 2; ++index) {
const SkOpSegment& segment = index ? *rh.fSegment : *fSegment;
SkIntersections i;
(*CurveIntersectRay[index ? rPts : lPts])(segment.pts(), rays[index], &i);
// SkASSERT(i.used() >= 1);
// if (i.used() <= 1) {
// continue;
// }
double tStart = segment.t(index ? rh.fStart : fStart);
double tEnd = segment.t(index ? rh.fComputedEnd : fComputedEnd);
bool testAscends = index ? rh.fStart < rh.fComputedEnd : fStart < fComputedEnd;
double t = testAscends ? 0 : 1;
for (int idx2 = 0; idx2 < i.used(); ++idx2) {
double testT = i[0][idx2];
if (!approximately_between_orderable(tStart, testT, tEnd)) {
continue;
}
if (approximately_equal_orderable(tStart, testT)) {
continue;
}
smallTs[index] = t = testAscends ? SkTMax(t, testT) : SkTMin(t, testT);
limited[index] = approximately_equal_orderable(t, tEnd);
}
}
#if 0
if (smallTs[0] < 0 && smallTs[1] < 0) { // if neither ray intersects, do endpoint sort
double m0xm1 = 0;
if (lVerb == SkPath::kLine_Verb) {
SkASSERT(rVerb != SkPath::kLine_Verb);
SkDVector m0 = rays[1][1] - fCurvePart[0];
SkDPoint endPt;
endPt.set(rh.fSegment->pts()[rh.fStart < rh.fEnd ? rPts : 0]);
SkDVector m1 = endPt - fCurvePart[0];
m0xm1 = m0.crossCheck(m1);
}
if (rVerb == SkPath::kLine_Verb) {
SkDPoint endPt;
endPt.set(fSegment->pts()[fStart < fEnd ? lPts : 0]);
SkDVector m0 = endPt - fCurvePart[0];
SkDVector m1 = rays[0][1] - fCurvePart[0];
m0xm1 = m0.crossCheck(m1);
}
if (m0xm1 != 0) {
return m0xm1 < 0;
}
}
#endif
bool sRayLonger = false;
SkDVector sCept = {0, 0};
double sCeptT = -1;
int sIndex = -1;
bool useIntersect = false;
for (int index = 0; index < 2; ++index) {
if (smallTs[index] < 0) {
continue;
}
const SkOpSegment& segment = index ? *rh.fSegment : *fSegment;
const SkDPoint& dPt = segment.dPtAtT(smallTs[index]);
SkDVector cept = dPt - rays[index][0];
// If this point is on the curve, it should have been detected earlier by ordinary
// curve intersection. This may be hard to determine in general, but for lines,
// the point could be close to or equal to its end, but shouldn't be near the start.
if ((index ? lPts : rPts) == 1) {
SkDVector total = rays[index][1] - rays[index][0];
if (cept.lengthSquared() * 2 < total.lengthSquared()) {
continue;
}
}
SkDVector end = rays[index][1] - rays[index][0];
if (cept.fX * end.fX < 0 || cept.fY * end.fY < 0) {
continue;
}
double rayDist = cept.length();
double endDist = end.length();
bool rayLonger = rayDist > endDist;
if (limited[0] && limited[1] && rayLonger) {
useIntersect = true;
sRayLonger = rayLonger;
sCept = cept;
sCeptT = smallTs[index];
sIndex = index;
break;
}
double delta = fabs(rayDist - endDist);
double minX, minY, maxX, maxY;
minX = minY = SK_ScalarInfinity;
maxX = maxY = -SK_ScalarInfinity;
const SkDCubic& curve = index ? rh.fCurvePart : fCurvePart;
int ptCount = index ? rPts : lPts;
for (int idx2 = 0; idx2 <= ptCount; ++idx2) {
minX = SkTMin(minX, curve[idx2].fX);
minY = SkTMin(minY, curve[idx2].fY);
maxX = SkTMax(maxX, curve[idx2].fX);
maxY = SkTMax(maxY, curve[idx2].fY);
}
double maxWidth = SkTMax(maxX - minX, maxY - minY);
delta /= maxWidth;
if (delta > 1e-4 && (useIntersect ^= true)) { // FIXME: move this magic number
sRayLonger = rayLonger;
sCept = cept;
sCeptT = smallTs[index];
sIndex = index;
}
}
if (useIntersect) {
const SkDCubic& curve = sIndex ? rh.fCurvePart : fCurvePart;
const SkOpSegment& segment = sIndex ? *rh.fSegment : *fSegment;
double tStart = segment.t(sIndex ? rh.fStart : fStart);
SkDVector mid = segment.dPtAtT(tStart + (sCeptT - tStart) / 2) - curve[0];
double septDir = mid.crossCheck(sCept);
if (!septDir) {
return checkParallel(rh);
}
return sRayLonger ^ (sIndex == 0) ^ (septDir < 0);
} else {
return checkParallel(rh);
}
}
// Most of the time, the first one can be found trivially by detecting the smallest sector value.
// If all angles have the same sector value, actual sorting is required.
const SkOpAngle* SkOpAngle::findFirst() const {
const SkOpAngle* best = this;
int bestStart = SkTMin(fSectorStart, fSectorEnd);
const SkOpAngle* angle = this;
while ((angle = angle->fNext) != this) {
int angleEnd = SkTMax(angle->fSectorStart, angle->fSectorEnd);
if (angleEnd < bestStart) {
return angle; // we wrapped around
}
int angleStart = SkTMin(angle->fSectorStart, angle->fSectorEnd);
if (bestStart > angleStart) {
best = angle;
bestStart = angleStart;
}
}
// back up to the first possible angle
const SkOpAngle* firstBest = best;
angle = best;
int bestEnd = SkTMax(best->fSectorStart, best->fSectorEnd);
while ((angle = angle->previous()) != firstBest) {
if (angle->fStop) {
break;
}
int angleStart = SkTMin(angle->fSectorStart, angle->fSectorEnd);
// angles that are smaller by one aren't necessary better, since the larger may be a line
// and the smaller may be a curve that curls to the other side of the line.
if (bestEnd + 1 < angleStart) {
return best;
}
best = angle;
bestEnd = SkTMax(angle->fSectorStart, angle->fSectorEnd);
}
// in the case where all angles are nearly in the same sector, check the order to find the best
firstBest = best;
angle = best;
do {
angle = angle->fNext;
if (angle->fStop) {
return firstBest;
}
bool orderable = best->orderable(*angle); // note: may return an unorderable angle
if (orderable == 0) {
return angle;
}
best = angle;
} while (angle != firstBest);
// if the angles are equally ordered, fall back on the initial tangent
bool foundBelow = false;
while ((angle = angle->fNext)) {
SkDVector scratch[2];
const SkDVector* sweep;
if (!angle->fUnorderedSweep) {
sweep = angle->fSweep;
} else {
scratch[0] = angle->fCurvePart[1] - angle->fCurvePart[0];
sweep = &scratch[0];
}
bool isAbove = sweep->fY <= 0;
if (isAbove && foundBelow) {
return angle;
}
foundBelow |= !isAbove;
if (angle == firstBest) {
return NULL; // should not loop around
}
}
SkASSERT(0); // should never get here
return NULL;
}
/* y<0 y==0 y>0 x<0 x==0 x>0 xy<0 xy==0 xy>0
0 x x x
1 x x x
2 x x x
3 x x x
4 x x x
5 x x x
6 x x x
7 x x x
8 x x x
9 x x x
10 x x x
11 x x x
12 x x x
13 x x x
14 x x x
15 x x x
*/
int SkOpAngle::findSector(SkPath::Verb verb, double x, double y) const {
double absX = fabs(x);
double absY = fabs(y);
double xy = SkPath::kLine_Verb == verb || !AlmostEqualUlps(absX, absY) ? absX - absY : 0;
// If there are four quadrants and eight octants, and since the Latin for sixteen is sedecim,
// one could coin the term sedecimant for a space divided into 16 sections.
// http://english.stackexchange.com/questions/133688/word-for-something-partitioned-into-16-parts
static const int sedecimant[3][3][3] = {
// y<0 y==0 y>0
// x<0 x==0 x>0 x<0 x==0 x>0 x<0 x==0 x>0
{{ 4, 3, 2}, { 7, -1, 15}, {10, 11, 12}}, // abs(x) < abs(y)
{{ 5, -1, 1}, {-1, -1, -1}, { 9, -1, 13}}, // abs(x) == abs(y)
{{ 6, 3, 0}, { 7, -1, 15}, { 8, 11, 14}}, // abs(x) > abs(y)
};
int sector = sedecimant[(xy >= 0) + (xy > 0)][(y >= 0) + (y > 0)][(x >= 0) + (x > 0)] * 2 + 1;
// SkASSERT(SkPath::kLine_Verb == verb || sector >= 0);
return sector;
}
// OPTIMIZE: if this loops to only one other angle, after first compare fails, insert on other side
// OPTIMIZE: return where insertion succeeded. Then, start next insertion on opposite side
void SkOpAngle::insert(SkOpAngle* angle) {
if (angle->fNext) {
if (loopCount() >= angle->loopCount()) {
if (!merge(angle)) {
return;
}
} else if (fNext) {
if (!angle->merge(this)) {
return;
}
} else {
angle->insert(this);
}
return;
}
bool singleton = NULL == fNext;
if (singleton) {
fNext = this;
}
SkOpAngle* next = fNext;
if (next->fNext == this) {
if (angle->overlap(*this)) {
return;
}
if (singleton || angle->after(this)) {
this->fNext = angle;
angle->fNext = next;
} else {
next->fNext = angle;
angle->fNext = this;
}
debugValidateNext();
return;
}
SkOpAngle* last = this;
do {
SkASSERT(last->fNext == next);
if (angle->overlap(*last) || angle->overlap(*next)) {
return;
}
if (angle->after(last)) {
last->fNext = angle;
angle->fNext = next;
debugValidateNext();
return;
}
last = next;
next = next->fNext;
if (last == this && next->fUnorderable) {
fUnorderable = true;
return;
}
SkASSERT(last != this);
} while (true);
}
bool SkOpAngle::isHorizontal() const {
return !fIsCurve && fSweep[0].fY == 0;
}
SkOpSpan* SkOpAngle::lastMarked() const {
if (fLastMarked) {
if (fLastMarked->fChased) {
return NULL;
}
fLastMarked->fChased = true;
}
return fLastMarked;
}
bool SkOpAngle::loopContains(const SkOpAngle& test) const {
if (!fNext) {
return false;
}
const SkOpAngle* first = this;
const SkOpAngle* loop = this;
const SkOpSegment* tSegment = test.fSegment;
double tStart = tSegment->span(test.fStart).fT;
double tEnd = tSegment->span(test.fEnd).fT;
do {
const SkOpSegment* lSegment = loop->fSegment;
// FIXME : use precisely_equal ? or compare points exactly ?
if (lSegment != tSegment) {
continue;
}
double lStart = lSegment->span(loop->fStart).fT;
if (lStart != tEnd) {
continue;
}
double lEnd = lSegment->span(loop->fEnd).fT;
if (lEnd == tStart) {
return true;
}
} while ((loop = loop->fNext) != first);
return false;
}
int SkOpAngle::loopCount() const {
int count = 0;
const SkOpAngle* first = this;
const SkOpAngle* next = this;
do {
next = next->fNext;
++count;
} while (next && next != first);
return count;
}
// OPTIMIZATION: can this be done better in after when angles are sorted?
void SkOpAngle::markStops() {
SkOpAngle* angle = this;
int lastEnd = SkTMax(fSectorStart, fSectorEnd);
do {
angle = angle->fNext;
int angleStart = SkTMin(angle->fSectorStart, angle->fSectorEnd);
// angles that are smaller by one aren't necessary better, since the larger may be a line
// and the smaller may be a curve that curls to the other side of the line.
if (lastEnd + 1 < angleStart) {
angle->fStop = true;
}
lastEnd = SkTMax(angle->fSectorStart, angle->fSectorEnd);
} while (angle != this);
}
bool SkOpAngle::merge(SkOpAngle* angle) {
SkASSERT(fNext);
SkASSERT(angle->fNext);
SkOpAngle* working = angle;
do {
if (this == working) {
return false;
}
working = working->fNext;
} while (working != angle);
do {
SkOpAngle* next = working->fNext;
working->fNext = NULL;
insert(working);
working = next;
} while (working != angle);
// it's likely that a pair of the angles are unorderable
#if DEBUG_ANGLE
SkOpAngle* last = angle;
working = angle->fNext;
do {
SkASSERT(last->fNext == working);
last->fNext = working->fNext;
SkASSERT(working->after(last));
last->fNext = working;
last = working;
working = working->fNext;
} while (last != angle);
#endif
debugValidateNext();
return true;
}
double SkOpAngle::midT() const {
return (fSegment->t(fStart) + fSegment->t(fEnd)) / 2;
}
bool SkOpAngle::oppositePlanes(const SkOpAngle& rh) const {
int startSpan = abs(rh.fSectorStart - fSectorStart);
return startSpan >= 8;
}
bool SkOpAngle::orderable(const SkOpAngle& rh) const {
int result;
if (!fIsCurve) {
if (!rh.fIsCurve) {
double leftX = fTangentHalf.dx();
double leftY = fTangentHalf.dy();
double rightX = rh.fTangentHalf.dx();
double rightY = rh.fTangentHalf.dy();
double x_ry = leftX * rightY;
double rx_y = rightX * leftY;
if (x_ry == rx_y) {
if (leftX * rightX < 0 || leftY * rightY < 0) {
return true; // exactly 180 degrees apart
}
goto unorderable;
}
SkASSERT(x_ry != rx_y); // indicates an undetected coincidence -- worth finding earlier
return x_ry < rx_y;
}
if ((result = allOnOneSide(rh)) >= 0) {
return result;
}
if (fUnorderable || approximately_zero(rh.fSide)) {
goto unorderable;
}
} else if (!rh.fIsCurve) {
if ((result = rh.allOnOneSide(*this)) >= 0) {
return !result;
}
if (rh.fUnorderable || approximately_zero(fSide)) {
goto unorderable;
}
}
if ((result = convexHullOverlaps(rh)) >= 0) {
return result;
}
return endsIntersect(rh);
unorderable:
fUnorderable = true;
rh.fUnorderable = true;
return true;
}
bool SkOpAngle::overlap(const SkOpAngle& other) const {
int min = SkTMin(fStart, fEnd);
const SkOpSpan& span = fSegment->span(min);
const SkOpSegment* oSeg = other.fSegment;
int oMin = SkTMin(other.fStart, other.fEnd);
const SkOpSpan& oSpan = oSeg->span(oMin);
if (!span.fSmall && !oSpan.fSmall) {
return false;
}
if (fSegment->span(fStart).fPt != oSeg->span(other.fStart).fPt) {
return false;
}
// see if small span is contained by opposite span
return span.fSmall ? oSeg->containsPt(fSegment->span(fEnd).fPt, other.fEnd, other.fStart)
: fSegment->containsPt(oSeg->span(other.fEnd).fPt, fEnd, fStart);
}
// OPTIMIZE: if this shows up in a profile, add a previous pointer
// as is, this should be rarely called
SkOpAngle* SkOpAngle::previous() const {
SkOpAngle* last = fNext;
do {
SkOpAngle* next = last->fNext;
if (next == this) {
return last;
}
last = next;
} while (true);
}
void SkOpAngle::set(const SkOpSegment* segment, int start, int end) {
fSegment = segment;
fStart = start;
fComputedEnd = fEnd = end;
fNext = NULL;
fComputeSector = fComputedSector = false;
fStop = false;
setSpans();
setSector();
}
void SkOpAngle::setCurveHullSweep() {
fUnorderedSweep = false;
fSweep[0] = fCurvePart[1] - fCurvePart[0];
if (SkPath::kLine_Verb == fSegment->verb()) {
fSweep[1] = fSweep[0];
return;
}
fSweep[1] = fCurvePart[2] - fCurvePart[0];
if (SkPath::kCubic_Verb != fSegment->verb()) {
if (!fSweep[0].fX && !fSweep[0].fY) {
fSweep[0] = fSweep[1];
}
return;
}
SkDVector thirdSweep = fCurvePart[3] - fCurvePart[0];
if (fSweep[0].fX == 0 && fSweep[0].fY == 0) {
fSweep[0] = fSweep[1];
fSweep[1] = thirdSweep;
if (fSweep[0].fX == 0 && fSweep[0].fY == 0) {
fSweep[0] = fSweep[1];
fCurvePart[1] = fCurvePart[3];
fIsCurve = false;
}
return;
}
double s1x3 = fSweep[0].crossCheck(thirdSweep);
double s3x2 = thirdSweep.crossCheck(fSweep[1]);
if (s1x3 * s3x2 >= 0) { // if third vector is on or between first two vectors
return;
}
double s2x1 = fSweep[1].crossCheck(fSweep[0]);
// FIXME: If the sweep of the cubic is greater than 180 degrees, we're in trouble
// probably such wide sweeps should be artificially subdivided earlier so that never happens
SkASSERT(s1x3 * s2x1 < 0 || s1x3 * s3x2 < 0);
if (s3x2 * s2x1 < 0) {
SkASSERT(s2x1 * s1x3 > 0);
fSweep[0] = fSweep[1];
fUnorderedSweep = true;
}
fSweep[1] = thirdSweep;
}
void SkOpAngle::setSector() {
SkPath::Verb verb = fSegment->verb();
if (SkPath::kLine_Verb != verb && small()) {
goto deferTilLater;
}
fSectorStart = findSector(verb, fSweep[0].fX, fSweep[0].fY);
if (fSectorStart < 0) {
goto deferTilLater;
}
if (!fIsCurve) { // if it's a line or line-like, note that both sectors are the same
SkASSERT(fSectorStart >= 0);
fSectorEnd = fSectorStart;
fSectorMask = 1 << fSectorStart;
return;
}
SkASSERT(SkPath::kLine_Verb != verb);
fSectorEnd = findSector(verb, fSweep[1].fX, fSweep[1].fY);
if (fSectorEnd < 0) {
deferTilLater:
fSectorStart = fSectorEnd = -1;
fSectorMask = 0;
fComputeSector = true; // can't determine sector until segment length can be found
return;
}
if (fSectorEnd == fSectorStart) {
SkASSERT((fSectorStart & 3) != 3); // if the sector has no span, it can't be an exact angle
fSectorMask = 1 << fSectorStart;
return;
}
bool crossesZero = checkCrossesZero();
int start = SkTMin(fSectorStart, fSectorEnd);
bool curveBendsCCW = (fSectorStart == start) ^ crossesZero;
// bump the start and end of the sector span if they are on exact compass points
if ((fSectorStart & 3) == 3) {
fSectorStart = (fSectorStart + (curveBendsCCW ? 1 : 31)) & 0x1f;
}
if ((fSectorEnd & 3) == 3) {
fSectorEnd = (fSectorEnd + (curveBendsCCW ? 31 : 1)) & 0x1f;
}
crossesZero = checkCrossesZero();
start = SkTMin(fSectorStart, fSectorEnd);
int end = SkTMax(fSectorStart, fSectorEnd);
if (!crossesZero) {
fSectorMask = (unsigned) -1 >> (31 - end + start) << start;
} else {
fSectorMask = (unsigned) -1 >> (31 - start) | (-1 << end);
}
}
void SkOpAngle::setSpans() {
fUnorderable = fSegment->isTiny(this);
fLastMarked = NULL;
const SkPoint* pts = fSegment->pts();
SkDEBUGCODE(fCurvePart[2].fX = fCurvePart[2].fY = fCurvePart[3].fX = fCurvePart[3].fY
= SK_ScalarNaN);
fSegment->subDivide(fStart, fEnd, &fCurvePart);
setCurveHullSweep();
const SkPath::Verb verb = fSegment->verb();
if (verb != SkPath::kLine_Verb
&& !(fIsCurve = fSweep[0].crossCheck(fSweep[1]) != 0)) {
SkDLine lineHalf;
lineHalf[0].set(fCurvePart[0].asSkPoint());
lineHalf[1].set(fCurvePart[SkPathOpsVerbToPoints(verb)].asSkPoint());
fTangentHalf.lineEndPoints(lineHalf);
fSide = 0;
}
switch (verb) {
case SkPath::kLine_Verb: {
SkASSERT(fStart != fEnd);
const SkPoint& cP1 = pts[fStart < fEnd];
SkDLine lineHalf;
lineHalf[0].set(fSegment->span(fStart).fPt);
lineHalf[1].set(cP1);
fTangentHalf.lineEndPoints(lineHalf);
fSide = 0;
fIsCurve = false;
} return;
case SkPath::kQuad_Verb: {
SkLineParameters tangentPart;
SkDQuad& quad2 = *SkTCast<SkDQuad*>(&fCurvePart);
(void) tangentPart.quadEndPoints(quad2);
fSide = -tangentPart.pointDistance(fCurvePart[2]); // not normalized -- compare sign only
} break;
case SkPath::kCubic_Verb: {
SkLineParameters tangentPart;
(void) tangentPart.cubicPart(fCurvePart);
fSide = -tangentPart.pointDistance(fCurvePart[3]);
double testTs[4];
// OPTIMIZATION: keep inflections precomputed with cubic segment?
int testCount = SkDCubic::FindInflections(pts, testTs);
double startT = fSegment->t(fStart);
double endT = fSegment->t(fEnd);
double limitT = endT;
int index;
for (index = 0; index < testCount; ++index) {
if (!::between(startT, testTs[index], limitT)) {
testTs[index] = -1;
}
}
testTs[testCount++] = startT;
testTs[testCount++] = endT;
SkTQSort<double>(testTs, &testTs[testCount - 1]);
double bestSide = 0;
int testCases = (testCount << 1) - 1;
index = 0;
while (testTs[index] < 0) {
++index;
}
index <<= 1;
for (; index < testCases; ++index) {
int testIndex = index >> 1;
double testT = testTs[testIndex];
if (index & 1) {
testT = (testT + testTs[testIndex + 1]) / 2;
}
// OPTIMIZE: could avoid call for t == startT, endT
SkDPoint pt = dcubic_xy_at_t(pts, testT);
SkLineParameters tangentPart;
tangentPart.cubicEndPoints(fCurvePart);
double testSide = tangentPart.pointDistance(pt);
if (fabs(bestSide) < fabs(testSide)) {
bestSide = testSide;
}
}
fSide = -bestSide; // compare sign only
} break;
default:
SkASSERT(0);
}
}
bool SkOpAngle::small() const {
int min = SkMin32(fStart, fEnd);
int max = SkMax32(fStart, fEnd);
for (int index = min; index < max; ++index) {
const SkOpSpan& mSpan = fSegment->span(index);
if (!mSpan.fSmall) {
return false;
}
}
return true;
}
bool SkOpAngle::tangentsDiverge(const SkOpAngle& rh, double s0xt0) const {
if (s0xt0 == 0) {
return false;
}
// if the ctrl tangents are not nearly parallel, use them
// solve for opposite direction displacement scale factor == m
// initial dir = v1.cross(v2) == v2.x * v1.y - v2.y * v1.x
// displacement of q1[1] : dq1 = { -m * v1.y, m * v1.x } + q1[1]
// straight angle when : v2.x * (dq1.y - q1[0].y) == v2.y * (dq1.x - q1[0].x)
// v2.x * (m * v1.x + v1.y) == v2.y * (-m * v1.y + v1.x)
// - m * (v2.x * v1.x + v2.y * v1.y) == v2.x * v1.y - v2.y * v1.x
// m = (v2.y * v1.x - v2.x * v1.y) / (v2.x * v1.x + v2.y * v1.y)
// m = v1.cross(v2) / v1.dot(v2)
const SkDVector* sweep = fSweep;
const SkDVector* tweep = rh.fSweep;
double s0dt0 = sweep[0].dot(tweep[0]);
if (!s0dt0) {
return true;
}
SkASSERT(s0dt0 != 0);
double m = s0xt0 / s0dt0;
double sDist = sweep[0].length() * m;
double tDist = tweep[0].length() * m;
bool useS = fabs(sDist) < fabs(tDist);
double mFactor = fabs(useS ? distEndRatio(sDist) : rh.distEndRatio(tDist));
return mFactor < 5000; // empirically found limit
}
SkOpAngleSet::SkOpAngleSet()
: fAngles(NULL)
#if DEBUG_ANGLE
, fCount(0)
#endif
{
}
SkOpAngleSet::~SkOpAngleSet() {
SkDELETE(fAngles);
}
SkOpAngle& SkOpAngleSet::push_back() {
if (!fAngles) {
fAngles = SkNEW_ARGS(SkChunkAlloc, (2));
}
void* ptr = fAngles->allocThrow(sizeof(SkOpAngle));
SkOpAngle* angle = (SkOpAngle*) ptr;
#if DEBUG_ANGLE
angle->setID(++fCount);
#endif
return *angle;
}
void SkOpAngleSet::reset() {
if (fAngles) {
fAngles->reset();
}
}