When solving the cubic line intersection directly fails, use binary search as a fallback.

The cubic line intersection math empirically works 99.99% of the time (fails 3100 out of 1B random tests) but when it fails, an intersection may be missed altogether.

The binary search is may not find a solution if the cubic line failed to find any solutions at all, but so far that case hasn't arisen.

BUG=skia:2504
TBR=reed@google.com

Author: caryclark@google.com

Review URL: https://codereview.chromium.org/266063003

git-svn-id: http://skia.googlecode.com/svn/trunk@14614 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
commit-bot@chromium.org 2014-05-07 15:31:40 +00:00
parent 2d91efffdb
commit 2db7fe7d3b
11 changed files with 538 additions and 37 deletions

View File

@ -23,6 +23,7 @@
], ],
'sources': [ 'sources': [
'../tests/PathOpsAngleIdeas.cpp', '../tests/PathOpsAngleIdeas.cpp',
'../tests/PathOpsCubicLineIntersectionIdeas.cpp',
'../tests/PathOpsDebug.cpp', '../tests/PathOpsDebug.cpp',
'../tests/PathOpsOpLoopThreadedTest.cpp', '../tests/PathOpsOpLoopThreadedTest.cpp',
'../tests/PathOpsSkpClipTest.cpp', '../tests/PathOpsSkpClipTest.cpp',

View File

@ -97,13 +97,27 @@ public:
int intersectRay(double roots[3]) { int intersectRay(double roots[3]) {
double adj = fLine[1].fX - fLine[0].fX; double adj = fLine[1].fX - fLine[0].fX;
double opp = fLine[1].fY - fLine[0].fY; double opp = fLine[1].fY - fLine[0].fY;
SkDCubic r; SkDCubic c;
for (int n = 0; n < 4; ++n) { for (int n = 0; n < 4; ++n) {
r[n].fX = (fCubic[n].fY - fLine[0].fY) * adj - (fCubic[n].fX - fLine[0].fX) * opp; c[n].fX = (fCubic[n].fY - fLine[0].fY) * adj - (fCubic[n].fX - fLine[0].fX) * opp;
} }
double A, B, C, D; double A, B, C, D;
SkDCubic::Coefficients(&r[0].fX, &A, &B, &C, &D); SkDCubic::Coefficients(&c[0].fX, &A, &B, &C, &D);
return SkDCubic::RootsValidT(A, B, C, D, roots); int count = SkDCubic::RootsValidT(A, B, C, D, roots);
for (int index = 0; index < count; ++index) {
SkDPoint calcPt = c.ptAtT(roots[index]);
if (!approximately_zero(calcPt.fX)) {
for (int n = 0; n < 4; ++n) {
c[n].fY = (fCubic[n].fY - fLine[0].fY) * opp
+ (fCubic[n].fX - fLine[0].fX) * adj;
}
double extremeTs[6];
int extrema = SkDCubic::FindExtrema(c[0].fX, c[1].fX, c[2].fX, c[3].fX, extremeTs);
count = c.searchRoots(extremeTs, extrema, 0, SkDCubic::kXAxis, roots);
break;
}
}
return count;
} }
int intersect() { int intersect() {
@ -146,11 +160,21 @@ public:
return fIntersections->used(); return fIntersections->used();
} }
int horizontalIntersect(double axisIntercept, double roots[3]) { static int HorizontalIntersect(const SkDCubic& c, double axisIntercept, double roots[3]) {
double A, B, C, D; double A, B, C, D;
SkDCubic::Coefficients(&fCubic[0].fY, &A, &B, &C, &D); SkDCubic::Coefficients(&c[0].fY, &A, &B, &C, &D);
D -= axisIntercept; D -= axisIntercept;
return SkDCubic::RootsValidT(A, B, C, D, roots); int count = SkDCubic::RootsValidT(A, B, C, D, roots);
for (int index = 0; index < count; ++index) {
SkDPoint calcPt = c.ptAtT(roots[index]);
if (!approximately_equal(calcPt.fY, axisIntercept)) {
double extremeTs[6];
int extrema = SkDCubic::FindExtrema(c[0].fY, c[1].fY, c[2].fY, c[3].fY, extremeTs);
count = c.searchRoots(extremeTs, extrema, axisIntercept, SkDCubic::kYAxis, roots);
break;
}
}
return count;
} }
int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) { int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) {
@ -158,11 +182,13 @@ public:
if (fAllowNear) { if (fAllowNear) {
addNearHorizontalEndPoints(left, right, axisIntercept); addNearHorizontalEndPoints(left, right, axisIntercept);
} }
double rootVals[3]; double roots[3];
int roots = horizontalIntersect(axisIntercept, rootVals); int count = HorizontalIntersect(fCubic, axisIntercept, roots);
for (int index = 0; index < roots; ++index) { for (int index = 0; index < count; ++index) {
double cubicT = rootVals[index]; double cubicT = roots[index];
SkDPoint pt = fCubic.ptAtT(cubicT); SkDPoint pt;
pt.fX = fCubic.ptAtT(cubicT).fX;
pt.fY = axisIntercept;
double lineT = (pt.fX - left) / (right - left); double lineT = (pt.fX - left) / (right - left);
if (pinTs(&cubicT, &lineT, &pt, kPointInitialized)) { if (pinTs(&cubicT, &lineT, &pt, kPointInitialized)) {
fIntersections->insert(cubicT, lineT, pt); fIntersections->insert(cubicT, lineT, pt);
@ -174,11 +200,21 @@ public:
return fIntersections->used(); return fIntersections->used();
} }
int verticalIntersect(double axisIntercept, double roots[3]) { static int VerticalIntersect(const SkDCubic& c, double axisIntercept, double roots[3]) {
double A, B, C, D; double A, B, C, D;
SkDCubic::Coefficients(&fCubic[0].fX, &A, &B, &C, &D); SkDCubic::Coefficients(&c[0].fX, &A, &B, &C, &D);
D -= axisIntercept; D -= axisIntercept;
return SkDCubic::RootsValidT(A, B, C, D, roots); int count = SkDCubic::RootsValidT(A, B, C, D, roots);
for (int index = 0; index < count; ++index) {
SkDPoint calcPt = c.ptAtT(roots[index]);
if (!approximately_equal(calcPt.fX, axisIntercept)) {
double extremeTs[6];
int extrema = SkDCubic::FindExtrema(c[0].fX, c[1].fX, c[2].fX, c[3].fX, extremeTs);
count = c.searchRoots(extremeTs, extrema, axisIntercept, SkDCubic::kXAxis, roots);
break;
}
}
return count;
} }
int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) { int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) {
@ -186,11 +222,13 @@ public:
if (fAllowNear) { if (fAllowNear) {
addNearVerticalEndPoints(top, bottom, axisIntercept); addNearVerticalEndPoints(top, bottom, axisIntercept);
} }
double rootVals[3]; double roots[3];
int roots = verticalIntersect(axisIntercept, rootVals); int count = VerticalIntersect(fCubic, axisIntercept, roots);
for (int index = 0; index < roots; ++index) { for (int index = 0; index < count; ++index) {
double cubicT = rootVals[index]; double cubicT = roots[index];
SkDPoint pt = fCubic.ptAtT(cubicT); SkDPoint pt;
pt.fX = axisIntercept;
pt.fY = fCubic.ptAtT(cubicT).fY;
double lineT = (pt.fY - top) / (bottom - top); double lineT = (pt.fY - top) / (bottom - top);
if (pinTs(&cubicT, &lineT, &pt, kPointInitialized)) { if (pinTs(&cubicT, &lineT, &pt, kPointInitialized)) {
fIntersections->insert(cubicT, lineT, pt); fIntersections->insert(cubicT, lineT, pt);

View File

@ -9,9 +9,58 @@
#include "SkPathOpsLine.h" #include "SkPathOpsLine.h"
#include "SkPathOpsQuad.h" #include "SkPathOpsQuad.h"
#include "SkPathOpsRect.h" #include "SkPathOpsRect.h"
#include "SkTSort.h"
const int SkDCubic::gPrecisionUnit = 256; // FIXME: test different values in test framework const int SkDCubic::gPrecisionUnit = 256; // FIXME: test different values in test framework
// give up when changing t no longer moves point
// also, copy point rather than recompute it when it does change
double SkDCubic::binarySearch(double min, double max, double axisIntercept,
SearchAxis xAxis) const {
double t = (min + max) / 2;
double step = (t - min) / 2;
SkDPoint cubicAtT = ptAtT(t);
double calcPos = (&cubicAtT.fX)[xAxis];
double calcDist = calcPos - axisIntercept;
do {
double priorT = t - step;
SkASSERT(priorT >= min);
SkDPoint lessPt = ptAtT(priorT);
if (approximately_equal(lessPt.fX, cubicAtT.fX)
&& approximately_equal(lessPt.fY, cubicAtT.fY)) {
return -1; // binary search found no point at this axis intercept
}
double lessDist = (&lessPt.fX)[xAxis] - axisIntercept;
#if DEBUG_CUBIC_BINARY_SEARCH
SkDebugf("t=%1.9g calc=%1.9g dist=%1.9g step=%1.9g less=%1.9g\n", t, calcPos, calcDist,
step, lessDist);
#endif
double lastStep = step;
step /= 2;
if (calcDist > 0 ? calcDist > lessDist : calcDist < lessDist) {
t = priorT;
} else {
double nextT = t + lastStep;
SkASSERT(nextT <= max);
SkDPoint morePt = ptAtT(nextT);
if (approximately_equal(morePt.fX, cubicAtT.fX)
&& approximately_equal(morePt.fY, cubicAtT.fY)) {
return -1; // binary search found no point at this axis intercept
}
double moreDist = (&morePt.fX)[xAxis] - axisIntercept;
if (calcDist > 0 ? calcDist <= moreDist : calcDist >= moreDist) {
continue;
}
t = nextT;
}
SkDPoint testAtT = ptAtT(t);
cubicAtT = testAtT;
calcPos = (&cubicAtT.fX)[xAxis];
calcDist = calcPos - axisIntercept;
} while (!approximately_equal(calcPos, axisIntercept));
return t;
}
// FIXME: cache keep the bounds and/or precision with the caller? // FIXME: cache keep the bounds and/or precision with the caller?
double SkDCubic::calcPrecision() const { double SkDCubic::calcPrecision() const {
SkDRect dRect; SkDRect dRect;
@ -93,6 +142,27 @@ bool SkDCubic::monotonicInY() const {
&& between(fPts[0].fY, fPts[2].fY, fPts[3].fY); && between(fPts[0].fY, fPts[2].fY, fPts[3].fY);
} }
int SkDCubic::searchRoots(double extremeTs[6], int extrema, double axisIntercept,
SearchAxis xAxis, double* validRoots) const {
extrema += findInflections(&extremeTs[extrema]);
extremeTs[extrema++] = 0;
extremeTs[extrema] = 1;
SkTQSort(extremeTs, extremeTs + extrema);
int validCount = 0;
for (int index = 0; index < extrema; ) {
double min = extremeTs[index];
double max = extremeTs[++index];
if (min == max) {
continue;
}
double newT = binarySearch(min, max, axisIntercept, xAxis);
if (newT >= 0) {
validRoots[validCount++] = newT;
}
}
return validCount;
}
bool SkDCubic::serpentine() const { bool SkDCubic::serpentine() const {
#if 0 // FIXME: enabling this fixes cubicOp114 but breaks cubicOp58d and cubicOp53d #if 0 // FIXME: enabling this fixes cubicOp114 but breaks cubicOp58d and cubicOp53d
double tValues[2]; double tValues[2];
@ -210,7 +280,7 @@ int SkDCubic::RootsReal(double A, double B, double C, double D, double s[3]) {
} }
r = A - adiv3; r = A - adiv3;
*roots++ = r; *roots++ = r;
if (AlmostDequalUlps(R2, Q3)) { if (AlmostDequalUlps((double) R2, (double) Q3)) {
r = -A / 2 - adiv3; r = -A / 2 - adiv3;
if (!AlmostDequalUlps(s[0], r)) { if (!AlmostDequalUlps(s[0], r)) {
*roots++ = r; *roots++ = r;

View File

@ -19,21 +19,16 @@ struct SkDCubicPair {
}; };
struct SkDCubic { struct SkDCubic {
SkDPoint fPts[4]; enum SearchAxis {
kXAxis,
void set(const SkPoint pts[4]) { kYAxis
fPts[0] = pts[0]; };
fPts[1] = pts[1];
fPts[2] = pts[2];
fPts[3] = pts[3];
}
static const int gPrecisionUnit;
const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < 4); return fPts[n]; } const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < 4); return fPts[n]; }
SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < 4); return fPts[n]; } SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < 4); return fPts[n]; }
void align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const; void align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const;
double binarySearch(double min, double max, double axisIntercept, SearchAxis xAxis) const;
double calcPrecision() const; double calcPrecision() const;
SkDCubicPair chopAt(double t) const; SkDCubicPair chopAt(double t) const;
bool clockwise() const; bool clockwise() const;
@ -42,9 +37,9 @@ struct SkDCubic {
SkDVector dxdyAtT(double t) const; SkDVector dxdyAtT(double t) const;
bool endsAreExtremaInXOrY() const; bool endsAreExtremaInXOrY() const;
static int FindExtrema(double a, double b, double c, double d, double tValue[2]); static int FindExtrema(double a, double b, double c, double d, double tValue[2]);
int findInflections(double tValues[]) const; int findInflections(double tValues[2]) const;
static int FindInflections(const SkPoint a[4], double tValues[]) { static int FindInflections(const SkPoint a[4], double tValues[2]) {
SkDCubic cubic; SkDCubic cubic;
cubic.set(a); cubic.set(a);
return cubic.findInflections(tValues); return cubic.findInflections(tValues);
@ -56,7 +51,18 @@ struct SkDCubic {
SkDPoint ptAtT(double t) const; SkDPoint ptAtT(double t) const;
static int RootsReal(double A, double B, double C, double D, double t[3]); static int RootsReal(double A, double B, double C, double D, double t[3]);
static int RootsValidT(const double A, const double B, const double C, double D, double s[3]); static int RootsValidT(const double A, const double B, const double C, double D, double s[3]);
int searchRoots(double extremes[6], int extrema, double axisIntercept,
SearchAxis xAxis, double* validRoots) const;
bool serpentine() const; bool serpentine() const;
void set(const SkPoint pts[4]) {
fPts[0] = pts[0];
fPts[1] = pts[1];
fPts[2] = pts[2];
fPts[3] = pts[3];
}
SkDCubic subDivide(double t1, double t2) const; SkDCubic subDivide(double t1, double t2) const;
static SkDCubic SubDivide(const SkPoint a[4], double t1, double t2) { static SkDCubic SubDivide(const SkPoint a[4], double t1, double t2) {
@ -81,6 +87,10 @@ struct SkDCubic {
// utilities callable by the user from the debugger when the implementation code is linked in // utilities callable by the user from the debugger when the implementation code is linked in
void dump() const; void dump() const;
void dumpNumber() const; void dumpNumber() const;
static const int gPrecisionUnit;
SkDPoint fPts[4];
}; };
#endif #endif

View File

@ -50,6 +50,7 @@
#define DEBUG_CHECK_TINY 0 #define DEBUG_CHECK_TINY 0
#define DEBUG_CONCIDENT 0 #define DEBUG_CONCIDENT 0
#define DEBUG_CROSS 0 #define DEBUG_CROSS 0
#define DEBUG_CUBIC_BINARY_SEARCH 0
#define DEBUG_FLAT_QUADS 0 #define DEBUG_FLAT_QUADS 0
#define DEBUG_FLOW 0 #define DEBUG_FLOW 0
#define DEBUG_LIMIT_WIND_SUM 0 #define DEBUG_LIMIT_WIND_SUM 0
@ -84,6 +85,7 @@
#define DEBUG_CHECK_TINY 1 #define DEBUG_CHECK_TINY 1
#define DEBUG_CONCIDENT 1 #define DEBUG_CONCIDENT 1
#define DEBUG_CROSS 01 #define DEBUG_CROSS 01
#define DEBUG_CUBIC_BINARY_SEARCH 1
#define DEBUG_FLAT_QUADS 0 #define DEBUG_FLAT_QUADS 0
#define DEBUG_FLOW 1 #define DEBUG_FLOW 1
#define DEBUG_LIMIT_WIND_SUM 4 #define DEBUG_LIMIT_WIND_SUM 4

View File

@ -102,6 +102,13 @@ bool AlmostDequalUlps(float a, float b) {
return d_equal_ulps(a, b, UlpsEpsilon); return d_equal_ulps(a, b, UlpsEpsilon);
} }
bool AlmostDequalUlps(double a, double b) {
if (SkScalarIsFinite(a) || SkScalarIsFinite(b)) {
return AlmostDequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b));
}
return fabs(a - b) / SkTMax(fabs(a), fabs(b)) < FLT_EPSILON * 16;
}
bool AlmostEqualUlps(float a, float b) { bool AlmostEqualUlps(float a, float b) {
const int UlpsEpsilon = 16; const int UlpsEpsilon = 16;
return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon); return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon);

View File

@ -30,9 +30,7 @@ inline bool AlmostEqualUlps(double a, double b) {
// Use Almost Dequal when comparing should not special case denormalized values. // Use Almost Dequal when comparing should not special case denormalized values.
bool AlmostDequalUlps(float a, float b); bool AlmostDequalUlps(float a, float b);
inline bool AlmostDequalUlps(double a, double b) { bool AlmostDequalUlps(double a, double b);
return AlmostDequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b));
}
bool NotAlmostEqualUlps(float a, float b); bool NotAlmostEqualUlps(float a, float b);
inline bool NotAlmostEqualUlps(double a, double b) { inline bool NotAlmostEqualUlps(double a, double b) {

View File

@ -0,0 +1,283 @@
/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "PathOpsTestCommon.h"
#include "SkIntersections.h"
#include "SkPathOpsCubic.h"
#include "SkPathOpsLine.h"
#include "SkPathOpsQuad.h"
#include "SkRandom.h"
#include "SkReduceOrder.h"
#include "Test.h"
static bool gPathOpsCubicLineIntersectionIdeasVerbose = false;
static struct CubicLineFailures {
SkDCubic c;
double t;
SkDPoint p;
} cubicLineFailures[] = {
{{{{-164.3726806640625, 36.826904296875}, {-189.045166015625, -953.2220458984375},
{926.505859375, -897.36175537109375}, {-139.33489990234375, 204.40771484375}}},
0.37329583, {107.54935269006289, -632.13736293162208}},
{{{{784.056884765625, -554.8350830078125}, {67.5489501953125, 509.0224609375},
{-447.713134765625, 751.375}, {415.7784423828125, 172.22265625}}},
0.660005242, {-32.973148967736151, 478.01341797403569}},
{{{{-580.6834716796875, -127.044921875}, {-872.8983154296875, -945.54302978515625},
{260.8092041015625, -909.34991455078125}, {-976.2125244140625, -18.46551513671875}}},
0.578826774, {-390.17910153915489, -687.21144412296007}},
};
int cubicLineFailuresCount = (int) SK_ARRAY_COUNT(cubicLineFailures);
double measuredSteps[] = {
9.15910731e-007, 8.6600277e-007, 7.4122059e-007, 6.92087618e-007, 8.35290245e-007,
3.29763199e-007, 5.07547773e-007, 4.41294224e-007, 0, 0,
3.76879167e-006, 1.06126249e-006, 2.36873967e-006, 1.62421134e-005, 3.09103599e-005,
4.38917976e-005, 0.000112348938, 0.000243149242, 0.000433174114, 0.00170880232,
0.00272619724, 0.00518844604, 0.000352621078, 0.00175960064, 0.027875185,
0.0351329803, 0.103964925,
};
/* last output : errors=3121
9.1796875e-007 8.59375e-007 7.5e-007 6.875e-007 8.4375e-007
3.125e-007 5e-007 4.375e-007 0 0
3.75e-006 1.09375e-006 2.1875e-006 1.640625e-005 3.0859375e-005
4.38964844e-005 0.000112304687 0.000243164063 0.000433181763 0.00170898437
0.00272619247 0.00518844604 0.000352621078 0.00175960064 0.027875185
0.0351329803 0.103964925
*/
static double binary_search(const SkDCubic& cubic, double step, const SkDPoint& pt, double t,
int* iters) {
double firstStep = step;
do {
*iters += 1;
SkDPoint cubicAtT = cubic.ptAtT(t);
if (cubicAtT.approximatelyEqual(pt)) {
break;
}
double calcX = cubicAtT.fX - pt.fX;
double calcY = cubicAtT.fY - pt.fY;
double calcDist = calcX * calcX + calcY * calcY;
if (step == 0) {
SkDebugf("binary search failed: step=%1.9g cubic=", firstStep);
cubic.dump();
SkDebugf(" t=%1.9g ", t);
pt.dump();
SkDebugf("\n");
return -1;
}
double lastStep = step;
step /= 2;
SkDPoint lessPt = cubic.ptAtT(t - lastStep);
double lessX = lessPt.fX - pt.fX;
double lessY = lessPt.fY - pt.fY;
double lessDist = lessX * lessX + lessY * lessY;
// use larger x/y difference to choose step
if (calcDist > lessDist) {
t -= step;
t = SkTMax(0., t);
} else {
SkDPoint morePt = cubic.ptAtT(t + lastStep);
double moreX = morePt.fX - pt.fX;
double moreY = morePt.fY - pt.fY;
double moreDist = moreX * moreX + moreY * moreY;
if (calcDist <= moreDist) {
continue;
}
t += step;
t = SkTMin(1., t);
}
} while (true);
return t;
}
#if 0
static bool r2check(double A, double B, double C, double D, double* R2MinusQ3Ptr) {
if (approximately_zero(A)
&& approximately_zero_when_compared_to(A, B)
&& approximately_zero_when_compared_to(A, C)
&& approximately_zero_when_compared_to(A, D)) { // we're just a quadratic
return false;
}
if (approximately_zero_when_compared_to(D, A)
&& approximately_zero_when_compared_to(D, B)
&& approximately_zero_when_compared_to(D, C)) { // 0 is one root
return false;
}
if (approximately_zero(A + B + C + D)) { // 1 is one root
return false;
}
double a, b, c;
{
double invA = 1 / A;
a = B * invA;
b = C * invA;
c = D * invA;
}
double a2 = a * a;
double Q = (a2 - b * 3) / 9;
double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
double R2 = R * R;
double Q3 = Q * Q * Q;
double R2MinusQ3 = R2 - Q3;
*R2MinusQ3Ptr = R2MinusQ3;
return true;
}
#endif
/* What is the relationship between the accuracy of the root in range and the magnitude of all
roots? To find out, create a bunch of cubics, and measure */
DEF_TEST(PathOpsCubicLineRoots, reporter) {
if (!gPathOpsCubicLineIntersectionIdeasVerbose) { // slow; exclude it by default
return;
}
SkRandom ran;
double worstStep[256] = {0};
int errors = 0;
int iters = 0;
double smallestR2 = 0;
double largestR2 = 0;
for (int index = 0; index < 1000000000; ++index) {
SkDPoint origin = {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)};
SkDCubic cubic = {{origin,
{ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
{ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)},
{ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}
}};
// construct a line at a known intersection
double t = ran.nextRangeF(0, 1);
SkDPoint pt = cubic.ptAtT(t);
// skip answers with no intersections (although note the bug!) or two, or more
// see if the line / cubic has a fun range of roots
double A, B, C, D;
SkDCubic::Coefficients(&cubic[0].fY, &A, &B, &C, &D);
D -= pt.fY;
double allRoots[3] = {0}, validRoots[3] = {0};
int realRoots = SkDCubic::RootsReal(A, B, C, D, allRoots);
int valid = SkDQuad::AddValidTs(allRoots, realRoots, validRoots);
if (valid != 1) {
continue;
}
if (realRoots == 1) {
continue;
}
t = validRoots[0];
SkDPoint calcPt = cubic.ptAtT(t);
if (calcPt.approximatelyEqual(pt)) {
continue;
}
#if 0
double R2MinusQ3;
if (r2check(A, B, C, D, &R2MinusQ3)) {
smallestR2 = SkTMin(smallestR2, R2MinusQ3);
largestR2 = SkTMax(largestR2, R2MinusQ3);
}
#endif
double largest = SkTMax(fabs(allRoots[0]), fabs(allRoots[1]));
if (realRoots == 3) {
largest = SkTMax(largest, fabs(allRoots[2]));
}
int largeBits;
if (largest <= 1) {
#if 0
SkDebugf("realRoots=%d (%1.9g, %1.9g, %1.9g) valid=%d (%1.9g, %1.9g, %1.9g)\n",
realRoots, allRoots[0], allRoots[1], allRoots[2], valid, validRoots[0],
validRoots[1], validRoots[2]);
#endif
double smallest = SkTMin(allRoots[0], allRoots[1]);
if (realRoots == 3) {
smallest = SkTMin(smallest, allRoots[2]);
}
SK_ALWAYSBREAK(smallest < 0);
SK_ALWAYSBREAK(smallest >= -1);
largeBits = 0;
} else {
frexp(largest, &largeBits);
SK_ALWAYSBREAK(largeBits >= 0);
SK_ALWAYSBREAK(largeBits < 256);
}
double step = 1e-6;
if (largeBits > 21) {
step = 1e-1;
} else if (largeBits > 18) {
step = 1e-2;
} else if (largeBits > 15) {
step = 1e-3;
} else if (largeBits > 12) {
step = 1e-4;
} else if (largeBits > 9) {
step = 1e-5;
}
double diff;
do {
double newT = binary_search(cubic, step, pt, t, &iters);
if (newT >= 0) {
diff = fabs(t - newT);
break;
}
step *= 1.5;
SK_ALWAYSBREAK(step < 1);
} while (true);
worstStep[largeBits] = SkTMax(worstStep[largeBits], diff);
#if 0
{
cubic.dump();
SkDebugf("\n");
SkDLine line = {{{pt.fX - 1, pt.fY}, {pt.fX + 1, pt.fY}}};
line.dump();
SkDebugf("\n");
}
#endif
++errors;
}
SkDebugf("errors=%d avgIter=%1.9g", errors, (double) iters / errors);
SkDebugf(" steps: ");
int worstLimit = SK_ARRAY_COUNT(worstStep);
while (worstStep[--worstLimit] == 0) ;
for (int idx2 = 0; idx2 <= worstLimit; ++idx2) {
SkDebugf("%1.9g ", worstStep[idx2]);
}
SkDebugf("\n");
SkDebugf("smallestR2=%1.9g largestR2=%1.9g\n", smallestR2, largestR2);
}
static double testOneFailure(const CubicLineFailures& failure) {
const SkDCubic& cubic = failure.c;
const SkDPoint& pt = failure.p;
double A, B, C, D;
SkDCubic::Coefficients(&cubic[0].fY, &A, &B, &C, &D);
D -= pt.fY;
double allRoots[3] = {0}, validRoots[3] = {0};
int realRoots = SkDCubic::RootsReal(A, B, C, D, allRoots);
int valid = SkDQuad::AddValidTs(allRoots, realRoots, validRoots);
SK_ALWAYSBREAK(valid == 1);
SK_ALWAYSBREAK(realRoots != 1);
double t = validRoots[0];
SkDPoint calcPt = cubic.ptAtT(t);
SK_ALWAYSBREAK(!calcPt.approximatelyEqual(pt));
int iters = 0;
double newT = binary_search(cubic, 0.1, pt, t, &iters);
return newT;
}
DEF_TEST(PathOpsCubicLineFailures, reporter) {
return; // disable for now
for (int index = 0; index < cubicLineFailuresCount; ++index) {
const CubicLineFailures& failure = cubicLineFailures[index];
double newT = testOneFailure(failure);
SK_ALWAYSBREAK(newT >= 0);
}
}
DEF_TEST(PathOpsCubicLineOneFailure, reporter) {
return; // disable for now
const CubicLineFailures& failure = cubicLineFailures[1];
double newT = testOneFailure(failure);
SK_ALWAYSBREAK(newT >= 0);
}

View File

@ -49,6 +49,13 @@ static void testFail(skiatest::Reporter* reporter, int iIndex) {
} }
static lineCubic lineCubicTests[] = { static lineCubic lineCubicTests[] = {
{{{{-634.60540771484375, -481.262939453125}, {266.2696533203125, -752.70867919921875},
{-751.8370361328125, -317.37921142578125}, {-969.7427978515625, 824.7255859375}}},
{{{-287.9506133720805678, -557.1376476615772617},
{-285.9506133720805678, -557.1376476615772617}}}},
{{{{36.7184372,0.888650894}, {36.7184372,0.888650894}, {35.1233864,0.554015458},
{34.5114098,-0.115255356}}}, {{{35.4531212,0}, {31.9375,0}}}},
{{{{421, 378}, {421, 380.209137f}, {418.761414f, 382}, {416, 382}}}, {{{{421, 378}, {421, 380.209137f}, {418.761414f, 382}, {416, 382}}},
{{{320, 378}, {421, 378.000031f}}}}, {{{320, 378}, {421, 378.000031f}}}},
@ -83,6 +90,32 @@ static lineCubic lineCubicTests[] = {
static const size_t lineCubicTests_count = SK_ARRAY_COUNT(lineCubicTests); static const size_t lineCubicTests_count = SK_ARRAY_COUNT(lineCubicTests);
static int doIntersect(SkIntersections& intersections, const SkDCubic& cubic, const SkDLine& line) {
int result;
bool flipped = false;
if (line[0].fX == line[1].fX) {
double top = line[0].fY;
double bottom = line[1].fY;
flipped = top > bottom;
if (flipped) {
SkTSwap<double>(top, bottom);
}
result = intersections.vertical(cubic, top, bottom, line[0].fX, flipped);
} else if (line[0].fY == line[1].fY) {
double left = line[0].fX;
double right = line[1].fX;
flipped = left > right;
if (flipped) {
SkTSwap<double>(left, right);
}
result = intersections.horizontal(cubic, left, right, line[0].fY, flipped);
} else {
intersections.intersect(cubic, line);
result = intersections.used();
}
return result;
}
static void testOne(skiatest::Reporter* reporter, int iIndex) { static void testOne(skiatest::Reporter* reporter, int iIndex) {
const SkDCubic& cubic = lineCubicTests[iIndex].cubic; const SkDCubic& cubic = lineCubicTests[iIndex].cubic;
SkASSERT(ValidCubic(cubic)); SkASSERT(ValidCubic(cubic));
@ -102,7 +135,7 @@ static void testOne(skiatest::Reporter* reporter, int iIndex) {
} }
if (order1 == 4 && order2 == 2) { if (order1 == 4 && order2 == 2) {
SkIntersections i; SkIntersections i;
int roots = i.intersect(cubic, line); int roots = doIntersect(i, cubic, line);
for (int pt = 0; pt < roots; ++pt) { for (int pt = 0; pt < roots; ++pt) {
double tt1 = i[0][pt]; double tt1 = i[0][pt];
SkDPoint xy1 = cubic.ptAtT(tt1); SkDPoint xy1 = cubic.ptAtT(tt1);

View File

@ -3329,10 +3329,30 @@ static void kari1(skiatest::Reporter* reporter, const char* filename) {
testPathOp(reporter, path1, path2, kDifference_PathOp, filename); testPathOp(reporter, path1, path2, kDifference_PathOp, filename);
} }
static void issue2504(skiatest::Reporter* reporter, const char* filename) {
SkPath path1;
path1.moveTo(34.2421875, -5.976562976837158203125);
path1.lineTo(35.453121185302734375, 0);
path1.lineTo(31.9375, 0);
path1.close();
SkPath path2;
path2.moveTo(36.71843719482421875, 0.8886508941650390625);
path2.cubicTo(36.71843719482421875, 0.8886508941650390625,
35.123386383056640625, 0.554015457630157470703125,
34.511409759521484375, -0.1152553558349609375);
path2.cubicTo(33.899425506591796875, -0.7845261096954345703125,
34.53484344482421875, -5.6777553558349609375,
34.53484344482421875, -5.6777553558349609375);
path2.close();
testPathOp(reporter, path1, path2, kUnion_PathOp, filename);
}
static void (*firstTest)(skiatest::Reporter* , const char* filename) = 0; static void (*firstTest)(skiatest::Reporter* , const char* filename) = 0;
static void (*stopTest)(skiatest::Reporter* , const char* filename) = 0; static void (*stopTest)(skiatest::Reporter* , const char* filename) = 0;
static struct TestDesc tests[] = { static struct TestDesc tests[] = {
TEST(issue2504),
TEST(kari1), TEST(kari1),
TEST(quadOp10i), TEST(quadOp10i),
#if 0 // FIXME: serpentine curve is ordered the wrong way #if 0 // FIXME: serpentine curve is ordered the wrong way

View File

@ -867,11 +867,50 @@ op intersect
{{{900.0235595703125, 551.60284423828125}, {900.06072998046875, 551.29705810546875}, {900.15655517578125, 551.0157470703125}}} {{{900.0235595703125, 551.60284423828125}, {900.06072998046875, 551.29705810546875}, {900.15655517578125, 551.0157470703125}}}
</div> </div>
<div id="cubicLineMiss1">
{{{-634.60540771484375, -481.262939453125}, {266.2696533203125, -752.70867919921875}, {-751.8370361328125, -317.37921142578125}, {-969.7427978515625, 824.7255859375}}}
{{{-287.9506133720805678, -557.1376476615772617}, {-285.9506133720805678, -557.1376476615772617}}}
</div>
<div id="cubicLineMiss2">
{{{-818.4456787109375, 248.218017578125}, {944.18505859375, -252.2330322265625}, {957.3946533203125, -45.43280029296875}, {-591.766357421875, 868.6187744140625}}}
{{{435.1963493079119871, -16.42683763243891093}, {437.1963493079119871, -16.42683763243891093}}}
</div>
<div id="cubicLineMiss3">
{{{-818.4456787109375, 248.218017578125}, {944.18505859375, -252.2330322265625}, {957.3946533203125, -45.43280029296875}, {-591.766357421875, 868.6187744140625}}}
{{{397.5007682490800676, -17.35020084021140008}, {399.5007682490800676, -17.35020084021140008}}}
</div>
<div id="cubicLineMiss4">
{{{-652.660888671875, -384.6475830078125}, {-551.7723388671875, -925.5025634765625}, {-321.06658935546875, -813.10345458984375}, {142.6982421875, -47.4503173828125}}}
{{{-478.4372049758064236, -717.868282575075682}, {-476.4372049758064236, -717.868282575075682}}}
</div>
<div id="cubicLineErr1">
{{{-954.4322509765625, 827.2216796875}, {-420.24017333984375, -7.80560302734375}, {799.134765625, -971.4295654296875}, {-556.23486328125, 344.400146484375}}}
{{{58.57411390280688579, -302.8879316712078662}, {60.57411390280688579, -302.8879316712078662}}}
</div>
<div id="cubicLineErr2">
{{{-634.60540771484375, -481.262939453125}, {266.2696533203125, -752.70867919921875}, {-751.8370361328125, -317.37921142578125}, {-969.7427978515625, 824.7255859375}}}
{{{-287.95061337208057, -557.13764766157726}, {-285.95061337208057, -557.13764766157726}}}
{{{-308.65463091760211, -549.4520029924679} -308.65463091760211, -569.4520029924679
</div>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
var testDivs = [ var testDivs = [
cubicLineErr2,
cubicLineErr1,
cubicLineMiss1,
cubicLineMiss2,
cubicLineMiss3,
cubicLineMiss4,
skpwww_pindosiya_com_99, skpwww_pindosiya_com_99,
self1, self1,
skpwww_seopack_blogspot_com_2153, skpwww_seopack_blogspot_com_2153,