1 /* 2 * Copyright 2014 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 #include "PathOpsTestCommon.h" 8 #include "SkIntersections.h" 9 #include "SkPathOpsCubic.h" 10 #include "SkPathOpsLine.h" 11 #include "SkPathOpsQuad.h" 12 #include "SkRandom.h" 13 #include "SkReduceOrder.h" 14 #include "Test.h" 15 16 static bool gPathOpsCubicLineIntersectionIdeasVerbose = false; 17 18 static struct CubicLineFailures { 19 CubicPts c; 20 double t; 21 SkDPoint p; 22 } cubicLineFailures[] = { 23 {{{{-164.3726806640625, 36.826904296875}, {-189.045166015625, -953.2220458984375}, 24 {926.505859375, -897.36175537109375}, {-139.33489990234375, 204.40771484375}}}, 25 0.37329583, {107.54935269006289, -632.13736293162208}}, 26 {{{{784.056884765625, -554.8350830078125}, {67.5489501953125, 509.0224609375}, 27 {-447.713134765625, 751.375}, {415.7784423828125, 172.22265625}}}, 28 0.660005242, {-32.973148967736151, 478.01341797403569}}, 29 {{{{-580.6834716796875, -127.044921875}, {-872.8983154296875, -945.54302978515625}, 30 {260.8092041015625, -909.34991455078125}, {-976.2125244140625, -18.46551513671875}}}, 31 0.578826774, {-390.17910153915489, -687.21144412296007}}, 32 }; 33 34 int cubicLineFailuresCount = (int) SK_ARRAY_COUNT(cubicLineFailures); 35 36 double measuredSteps[] = { 37 9.15910731e-007, 8.6600277e-007, 7.4122059e-007, 6.92087618e-007, 8.35290245e-007, 38 3.29763199e-007, 5.07547773e-007, 4.41294224e-007, 0, 0, 39 3.76879167e-006, 1.06126249e-006, 2.36873967e-006, 1.62421134e-005, 3.09103599e-005, 40 4.38917976e-005, 0.000112348938, 0.000243149242, 0.000433174114, 0.00170880232, 41 0.00272619724, 0.00518844604, 0.000352621078, 0.00175960064, 0.027875185, 42 0.0351329803, 0.103964925, 43 }; 44 45 /* last output : errors=3121 46 9.1796875e-007 8.59375e-007 7.5e-007 6.875e-007 8.4375e-007 47 3.125e-007 5e-007 4.375e-007 0 0 48 3.75e-006 1.09375e-006 2.1875e-006 1.640625e-005 3.0859375e-005 49 4.38964844e-005 0.000112304687 0.000243164063 0.000433181763 0.00170898437 50 0.00272619247 0.00518844604 0.000352621078 0.00175960064 0.027875185 51 0.0351329803 0.103964925 52 */ 53 54 static double binary_search(const SkDCubic& cubic, double step, const SkDPoint& pt, double t, 55 int* iters) { 56 double firstStep = step; 57 do { 58 *iters += 1; 59 SkDPoint cubicAtT = cubic.ptAtT(t); 60 if (cubicAtT.approximatelyEqual(pt)) { 61 break; 62 } 63 double calcX = cubicAtT.fX - pt.fX; 64 double calcY = cubicAtT.fY - pt.fY; 65 double calcDist = calcX * calcX + calcY * calcY; 66 if (step == 0) { 67 SkDebugf("binary search failed: step=%1.9g cubic=", firstStep); 68 cubic.dump(); 69 SkDebugf(" t=%1.9g ", t); 70 pt.dump(); 71 SkDebugf("\n"); 72 return -1; 73 } 74 double lastStep = step; 75 step /= 2; 76 SkDPoint lessPt = cubic.ptAtT(t - lastStep); 77 double lessX = lessPt.fX - pt.fX; 78 double lessY = lessPt.fY - pt.fY; 79 double lessDist = lessX * lessX + lessY * lessY; 80 // use larger x/y difference to choose step 81 if (calcDist > lessDist) { 82 t -= step; 83 t = SkTMax(0., t); 84 } else { 85 SkDPoint morePt = cubic.ptAtT(t + lastStep); 86 double moreX = morePt.fX - pt.fX; 87 double moreY = morePt.fY - pt.fY; 88 double moreDist = moreX * moreX + moreY * moreY; 89 if (calcDist <= moreDist) { 90 continue; 91 } 92 t += step; 93 t = SkTMin(1., t); 94 } 95 } while (true); 96 return t; 97 } 98 99 #if 0 100 static bool r2check(double A, double B, double C, double D, double* R2MinusQ3Ptr) { 101 if (approximately_zero(A) 102 && approximately_zero_when_compared_to(A, B) 103 && approximately_zero_when_compared_to(A, C) 104 && approximately_zero_when_compared_to(A, D)) { // we're just a quadratic 105 return false; 106 } 107 if (approximately_zero_when_compared_to(D, A) 108 && approximately_zero_when_compared_to(D, B) 109 && approximately_zero_when_compared_to(D, C)) { // 0 is one root 110 return false; 111 } 112 if (approximately_zero(A + B + C + D)) { // 1 is one root 113 return false; 114 } 115 double a, b, c; 116 { 117 double invA = 1 / A; 118 a = B * invA; 119 b = C * invA; 120 c = D * invA; 121 } 122 double a2 = a * a; 123 double Q = (a2 - b * 3) / 9; 124 double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54; 125 double R2 = R * R; 126 double Q3 = Q * Q * Q; 127 double R2MinusQ3 = R2 - Q3; 128 *R2MinusQ3Ptr = R2MinusQ3; 129 return true; 130 } 131 #endif 132 133 /* What is the relationship between the accuracy of the root in range and the magnitude of all 134 roots? To find out, create a bunch of cubics, and measure */ 135 136 DEF_TEST(PathOpsCubicLineRoots, reporter) { 137 if (!gPathOpsCubicLineIntersectionIdeasVerbose) { // slow; exclude it by default 138 return; 139 } 140 SkRandom ran; 141 double worstStep[256] = {0}; 142 int errors = 0; 143 int iters = 0; 144 double smallestR2 = 0; 145 double largestR2 = 0; 146 for (int index = 0; index < 1000000000; ++index) { 147 SkDPoint origin = {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}; 148 CubicPts cuPts = {{origin, 149 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}, 150 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}, 151 {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)} 152 }}; 153 // construct a line at a known intersection 154 double t = ran.nextRangeF(0, 1); 155 SkDCubic cubic; 156 cubic.debugSet(cuPts.fPts); 157 SkDPoint pt = cubic.ptAtT(t); 158 // skip answers with no intersections (although note the bug!) or two, or more 159 // see if the line / cubic has a fun range of roots 160 double A, B, C, D; 161 SkDCubic::Coefficients(&cubic[0].fY, &A, &B, &C, &D); 162 D -= pt.fY; 163 double allRoots[3] = {0}, validRoots[3] = {0}; 164 int realRoots = SkDCubic::RootsReal(A, B, C, D, allRoots); 165 int valid = SkDQuad::AddValidTs(allRoots, realRoots, validRoots); 166 if (valid != 1) { 167 continue; 168 } 169 if (realRoots == 1) { 170 continue; 171 } 172 t = validRoots[0]; 173 SkDPoint calcPt = cubic.ptAtT(t); 174 if (calcPt.approximatelyEqual(pt)) { 175 continue; 176 } 177 #if 0 178 double R2MinusQ3; 179 if (r2check(A, B, C, D, &R2MinusQ3)) { 180 smallestR2 = SkTMin(smallestR2, R2MinusQ3); 181 largestR2 = SkTMax(largestR2, R2MinusQ3); 182 } 183 #endif 184 double largest = SkTMax(fabs(allRoots[0]), fabs(allRoots[1])); 185 if (realRoots == 3) { 186 largest = SkTMax(largest, fabs(allRoots[2])); 187 } 188 int largeBits; 189 if (largest <= 1) { 190 #if 0 191 SkDebugf("realRoots=%d (%1.9g, %1.9g, %1.9g) valid=%d (%1.9g, %1.9g, %1.9g)\n", 192 realRoots, allRoots[0], allRoots[1], allRoots[2], valid, validRoots[0], 193 validRoots[1], validRoots[2]); 194 #endif 195 double smallest = SkTMin(allRoots[0], allRoots[1]); 196 if (realRoots == 3) { 197 smallest = SkTMin(smallest, allRoots[2]); 198 } 199 SkASSERT_RELEASE(smallest < 0); 200 SkASSERT_RELEASE(smallest >= -1); 201 largeBits = 0; 202 } else { 203 frexp(largest, &largeBits); 204 SkASSERT_RELEASE(largeBits >= 0); 205 SkASSERT_RELEASE(largeBits < 256); 206 } 207 double step = 1e-6; 208 if (largeBits > 21) { 209 step = 1e-1; 210 } else if (largeBits > 18) { 211 step = 1e-2; 212 } else if (largeBits > 15) { 213 step = 1e-3; 214 } else if (largeBits > 12) { 215 step = 1e-4; 216 } else if (largeBits > 9) { 217 step = 1e-5; 218 } 219 double diff; 220 do { 221 double newT = binary_search(cubic, step, pt, t, &iters); 222 if (newT >= 0) { 223 diff = fabs(t - newT); 224 break; 225 } 226 step *= 1.5; 227 SkASSERT_RELEASE(step < 1); 228 } while (true); 229 worstStep[largeBits] = SkTMax(worstStep[largeBits], diff); 230 #if 0 231 { 232 cubic.dump(); 233 SkDebugf("\n"); 234 SkDLine line = {{{pt.fX - 1, pt.fY}, {pt.fX + 1, pt.fY}}}; 235 line.dump(); 236 SkDebugf("\n"); 237 } 238 #endif 239 ++errors; 240 } 241 SkDebugf("errors=%d avgIter=%1.9g", errors, (double) iters / errors); 242 SkDebugf(" steps: "); 243 int worstLimit = SK_ARRAY_COUNT(worstStep); 244 while (worstStep[--worstLimit] == 0) ; 245 for (int idx2 = 0; idx2 <= worstLimit; ++idx2) { 246 SkDebugf("%1.9g ", worstStep[idx2]); 247 } 248 SkDebugf("\n"); 249 SkDebugf("smallestR2=%1.9g largestR2=%1.9g\n", smallestR2, largestR2); 250 } 251 252 static double testOneFailure(const CubicLineFailures& failure) { 253 const CubicPts& c = failure.c; 254 SkDCubic cubic; 255 cubic.debugSet(c.fPts); 256 const SkDPoint& pt = failure.p; 257 double A, B, C, D; 258 SkDCubic::Coefficients(&cubic[0].fY, &A, &B, &C, &D); 259 D -= pt.fY; 260 double allRoots[3] = {0}, validRoots[3] = {0}; 261 int realRoots = SkDCubic::RootsReal(A, B, C, D, allRoots); 262 int valid = SkDQuad::AddValidTs(allRoots, realRoots, validRoots); 263 SkASSERT_RELEASE(valid == 1); 264 SkASSERT_RELEASE(realRoots != 1); 265 double t = validRoots[0]; 266 SkDPoint calcPt = cubic.ptAtT(t); 267 SkASSERT_RELEASE(!calcPt.approximatelyEqual(pt)); 268 int iters = 0; 269 double newT = binary_search(cubic, 0.1, pt, t, &iters); 270 return newT; 271 } 272 273 DEF_TEST(PathOpsCubicLineFailures, reporter) { 274 return; // disable for now 275 for (int index = 0; index < cubicLineFailuresCount; ++index) { 276 const CubicLineFailures& failure = cubicLineFailures[index]; 277 double newT = testOneFailure(failure); 278 SkASSERT_RELEASE(newT >= 0); 279 } 280 } 281 282 DEF_TEST(PathOpsCubicLineOneFailure, reporter) { 283 return; // disable for now 284 const CubicLineFailures& failure = cubicLineFailures[1]; 285 double newT = testOneFailure(failure); 286 SkASSERT_RELEASE(newT >= 0); 287 } 288