From b45a1b46ee25e9b19800b028bb1ca925212ac7b4 Mon Sep 17 00:00:00 2001 From: "caryclark@google.com" Date: Fri, 18 May 2012 20:50:33 +0000 Subject: [PATCH] shape ops work in progress git-svn-id: http://skia.googlecode.com/svn/trunk@4006 2bbb7eff-a529-9590-31e7-b0007b416f81 --- experimental/Intersection/ActiveEdge_Test.cpp | 12 +- .../CubicIntersection_TestData.cpp | 1 + .../Intersection/CubicIntersection_TestData.h | 4 + .../Intersection/CubicReduceOrder.cpp | 4 +- experimental/Intersection/DataTypes.cpp | 4 + experimental/Intersection/DataTypes.h | 52 +- experimental/Intersection/EdgeMain.cpp | 14 +- experimental/Intersection/EdgeMain.h | 6 - experimental/Intersection/EdgeWalker.cpp | 8 +- .../EdgeWalkerPolygons_Mismatches.h | 9 - .../Intersection/EdgeWalkerPolygons_Test.cpp | 338 +++---- .../Intersection/Intersection_Tests.cpp | 9 +- .../Intersection/Intersection_Tests.h | 7 + experimental/Intersection/LineParameters.h | 12 +- .../Intersection/QuadraticBezierClip_Test.cpp | 24 +- .../QuadraticIntersection_Test.cpp | 49 +- .../QuadraticIntersection_TestData.cpp | 8 +- .../QuadraticIntersection_TestData.h | 4 + .../Intersection/QuadraticReduceOrder.cpp | 9 +- .../QuadraticReduceOrder_Test.cpp | 26 +- experimental/Intersection/Simplify.cpp | 828 +++++++++++------- experimental/Intersection/Simplify.h | 18 + .../SimplifyAddIntersectingTs_Test.cpp | 17 +- .../Intersection/SimplifyAngle_Test.cpp | 183 ++++ .../Intersection/SimplifyFindNext_Test.cpp | 83 ++ .../Intersection/SimplifyFindTop_Test.cpp | 208 +++++ experimental/Intersection/op.htm | 35 +- experimental/Intersection/thingsToDo.txt | 25 + gyp/shapeops_edge.gyp | 6 +- 29 files changed, 1455 insertions(+), 548 deletions(-) delete mode 100644 experimental/Intersection/EdgeMain.h delete mode 100644 experimental/Intersection/EdgeWalkerPolygons_Mismatches.h create mode 100644 experimental/Intersection/Simplify.h create mode 100644 experimental/Intersection/SimplifyAngle_Test.cpp create mode 100644 experimental/Intersection/SimplifyFindNext_Test.cpp create mode 100644 experimental/Intersection/SimplifyFindTop_Test.cpp diff --git a/experimental/Intersection/ActiveEdge_Test.cpp b/experimental/Intersection/ActiveEdge_Test.cpp index 03cb59574..3e1b958ab 100755 --- a/experimental/Intersection/ActiveEdge_Test.cpp +++ b/experimental/Intersection/ActiveEdge_Test.cpp @@ -25,24 +25,24 @@ SkPoint leftRight[][4] = { {{10, 0}, {10, 50}, {20, 10}, {20, 50}}, {{10, 0}, {10, 50}, {10, 10}, {20, 50}}, {{10, 0}, {10, 50}, {20, 10}, {10, 50}}, - {{10, 0}, {10, 50}, {20, 10}, {10 + 0.000001, 40}}, + {{10, 0}, {10, 50}, {20, 10}, {10 + 0.000001f, 40}}, // left top lower {{10, 20}, {10, 50}, {20, 10}, {20, 50}}, {{10, 20}, {10, 50}, {10, 10}, {20, 50}}, {{10, 20}, {10, 50}, {20, 10}, {10, 50}}, - {{10, 20}, {10, 50}, {20, 10}, {10 + 0.000001, 40}}, + {{10, 20}, {10, 50}, {20, 10}, {10 + 0.000001f, 40}}, {{10, 20}, {10, 50}, { 0, 0}, {50, 50}}, // left bottom higher {{10, 10}, {10, 40}, {20, 10}, {20, 50}}, {{10, 10}, {10, 40}, {10, 10}, {20, 50}}, {{10, 10}, {10, 40}, {20, 10}, {10, 50}}, - {{10, 10}, {10, 40}, {20, 10}, { 0 + 0.000001, 70}}, + {{10, 10}, {10, 40}, {20, 10}, { 0 + 0.000001f, 70}}, // left bottom lower {{10, 10}, {10, 60}, {20, 10}, {20, 50}}, {{10, 10}, {10, 60}, {10, 10}, {20, 50}}, - {{10, 10}, {10, 60}, {20, 10}, {10 + 0.000001, 50}}, - {{10, 10}, {10, 60}, {20, 10}, {10 + 0.000001, 40}}, - {{10, 10}, {10, 60}, { 0, 0}, {20 + 0.000001, 20}}, + {{10, 10}, {10, 60}, {20, 10}, {10 + 0.000001f, 50}}, + {{10, 10}, {10, 60}, {20, 10}, {10 + 0.000001f, 40}}, + {{10, 10}, {10, 60}, { 0, 0}, {20 + 0.000001f, 20}}, }; size_t leftRightCount = sizeof(leftRight) / sizeof(leftRight[0]); diff --git a/experimental/Intersection/CubicIntersection_TestData.cpp b/experimental/Intersection/CubicIntersection_TestData.cpp index 05d08afb9..066d9b5b9 100644 --- a/experimental/Intersection/CubicIntersection_TestData.cpp +++ b/experimental/Intersection/CubicIntersection_TestData.cpp @@ -1,3 +1,4 @@ +#define IN_TEST 1 #include "CubicIntersection_TestData.h" #include diff --git a/experimental/Intersection/CubicIntersection_TestData.h b/experimental/Intersection/CubicIntersection_TestData.h index 6ed0f49a7..7dc63a031 100644 --- a/experimental/Intersection/CubicIntersection_TestData.h +++ b/experimental/Intersection/CubicIntersection_TestData.h @@ -1,3 +1,7 @@ +#if !defined(IN_TEST) + #define IN_TEST 1 +#endif + #include "DataTypes.h" extern const Cubic pointDegenerates[]; diff --git a/experimental/Intersection/CubicReduceOrder.cpp b/experimental/Intersection/CubicReduceOrder.cpp index 9c3b843c9..19c69db94 100644 --- a/experimental/Intersection/CubicReduceOrder.cpp +++ b/experimental/Intersection/CubicReduceOrder.cpp @@ -150,12 +150,12 @@ bool isLinear(const Cubic& cubic, int startIndex, int endIndex) { int inner1 = startIndex ^ mask; int inner2 = endIndex ^ mask; lineParameters.controlPtDistance(cubic, inner1, inner2, distance); - double limit = normalSquared * SquaredEpsilon; + double limit = normalSquared; int index; for (index = 0; index < 2; ++index) { double distSq = distance[index]; distSq *= distSq; - if (distSq > limit) { + if (approximately_greater(distSq, limit)) { return false; } } diff --git a/experimental/Intersection/DataTypes.cpp b/experimental/Intersection/DataTypes.cpp index a68cb9a96..776d0cf99 100644 --- a/experimental/Intersection/DataTypes.cpp +++ b/experimental/Intersection/DataTypes.cpp @@ -14,8 +14,12 @@ void *memcpy(void *, const void *, size_t); #endif +#if USE_EPSILON const double PointEpsilon = 0.000001; const double SquaredEpsilon = PointEpsilon * PointEpsilon; +#endif + +const int UlpsEpsilon = 16; bool rotate(const Cubic& cubic, int zero, int index, Cubic& rotPath) { double dy = cubic[index].y - cubic[zero].y; diff --git a/experimental/Intersection/DataTypes.h b/experimental/Intersection/DataTypes.h index 290a336e3..8b7ff7fb3 100644 --- a/experimental/Intersection/DataTypes.h +++ b/experimental/Intersection/DataTypes.h @@ -12,6 +12,13 @@ #include #include +bool AlmostEqualUlps(float A, float B, int maxUlpsDiff); +int UlpsDiff(float A, float B); +int FloatAsInt(float A); + +#define USE_EPSILON 0 + +#if USE_EPSILON extern const double PointEpsilon; extern const double SquaredEpsilon; @@ -42,6 +49,48 @@ inline bool approximately_zero_squared(double x) { inline bool approximately_negative(double x) { return x < PointEpsilon; } +#else +extern const int UlpsEpsilon; + +#if defined(IN_TEST) +// FIXME: move to test-only header +const double PointEpsilon = 0.000001; +const double SquaredEpsilon = PointEpsilon * PointEpsilon; +#endif + +inline bool approximately_zero(double x) { + + return fabs(x) < FLT_EPSILON; +} + +inline bool approximately_equal(double x, double y) { + if (approximately_zero(x - y)) { + return true; + } + return AlmostEqualUlps((float) x, (float) y, UlpsEpsilon); +} + +inline bool approximately_equal_squared(double x, double y) { + return approximately_equal(x, y); +} + +inline bool approximately_greater(double x, double y) { + return approximately_equal(x, y) ? false : x > y; +} + +inline bool approximately_lesser(double x, double y) { + return approximately_equal(x, y) ? false : x < y; +} + +inline bool approximately_zero_squared(double x) { + return approximately_zero(x); +} + +inline bool approximately_negative(double x) { + return x < FLT_EPSILON; +} + +#endif struct _Point { double x; @@ -134,8 +183,5 @@ void xy_at_t(const Cubic& , double t, double& x, double& y); void xy_at_t(const _Line& , double t, double& x, double& y); void xy_at_t(const Quadratic& , double t, double& x, double& y); -bool AlmostEqualUlps(float A, float B, int maxUlpsDiff); -int UlpsDiff(float A, float B); -int FloatAsInt(float A); #endif // __DataTypes_h__ diff --git a/experimental/Intersection/EdgeMain.cpp b/experimental/Intersection/EdgeMain.cpp index a87a5cdd5..0042c9e61 100644 --- a/experimental/Intersection/EdgeMain.cpp +++ b/experimental/Intersection/EdgeMain.cpp @@ -1,11 +1,13 @@ /* - * EdgeMain.cpp - * shapeops_edge - * - * Created by Cary Clark on 3/26/12. - * Copyright 2012 __MyCompanyName__. All rights reserved. + * 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 "EdgeMain.h" +#include "Intersection_Tests.h" +int main(int argc, char* argv) { + Intersection_Tests(); + return 0; +} \ No newline at end of file diff --git a/experimental/Intersection/EdgeMain.h b/experimental/Intersection/EdgeMain.h deleted file mode 100644 index 0ab897c07..000000000 --- a/experimental/Intersection/EdgeMain.h +++ /dev/null @@ -1,6 +0,0 @@ -#include "Intersection_Tests.h" - -int main(int argc, char* argv) { - Intersection_Tests(); - return 0; -} \ No newline at end of file diff --git a/experimental/Intersection/EdgeWalker.cpp b/experimental/Intersection/EdgeWalker.cpp index 002e84c59..4aff58f4a 100644 --- a/experimental/Intersection/EdgeWalker.cpp +++ b/experimental/Intersection/EdgeWalker.cpp @@ -767,6 +767,7 @@ public: fTs = src.fTs; fTopIntercepts = src.fTopIntercepts; fBottomIntercepts = src.fBottomIntercepts; + return *this; } // OPTIMIZATION: remove this function if it's never called @@ -1409,7 +1410,8 @@ public: curve[1] = &rh.fTangent; curve[2] = &rh.fBelow; } - + // FIXME: code has been abandoned, incomplete.... + return false; } bool abCompare(const SkPoint& a1, const SkPoint& a2, const SkPoint& b1, @@ -1727,9 +1729,9 @@ public: b2 = edge->fTangent.fX; } if (fabs(t1 - t2) > fabs(b1 - b2)) { - ulps = UlpsDiff(t1, t2); + ulps = UlpsDiff((float) t1, (float) t2); } else { - ulps = UlpsDiff(b1, b2); + ulps = UlpsDiff((float) b1, (float) b2); } #if DEBUG_ADJUST_COINCIDENT SkDebugf("%s this=%d edge=%d ulps=%d\n", __FUNCTION__, ID(), edge->ID(), diff --git a/experimental/Intersection/EdgeWalkerPolygons_Mismatches.h b/experimental/Intersection/EdgeWalkerPolygons_Mismatches.h deleted file mode 100644 index d0381723a..000000000 --- a/experimental/Intersection/EdgeWalkerPolygons_Mismatches.h +++ /dev/null @@ -1,9 +0,0 @@ -/* - * EdgeWalkerPolygons_Mismatches.h - * edge - * - * Created by Cary Clark on 3/6/12. - * Copyright 2012 __MyCompanyName__. All rights reserved. - * - */ - diff --git a/experimental/Intersection/EdgeWalkerPolygons_Test.cpp b/experimental/Intersection/EdgeWalkerPolygons_Test.cpp index f3455eec3..80ef5270c 100644 --- a/experimental/Intersection/EdgeWalkerPolygons_Test.cpp +++ b/experimental/Intersection/EdgeWalkerPolygons_Test.cpp @@ -367,7 +367,7 @@ static void testPathTriangleRendering() { one.lineTo(0, 3); one.lineTo(1, 2); one.close(); - for (float x = .1f; x <= 2.9f; x += .1f) { + for (float x = .1f; x <= 2.9ff; x += .1f) { SkDebugf("%s x=%g\n", __FUNCTION__, x); two.moveTo(0, 0); two.lineTo(x, x); @@ -412,25 +412,25 @@ static void testSimplifySkinnyTriangle1() { static void testSimplifySkinnyTriangle2() { SkPath path, out; #if 01 -path.moveTo(591.091064, 627.534851); -path.lineTo(541.088135, 560.707642); -path.lineTo(491.085175, 493.880310); -path.lineTo(441.082214, 427.053101); -//path.lineTo(591.091064, 627.534851); +path.moveTo(591.091064f, 627.534851f); +path.lineTo(541.088135f, 560.707642f); +path.lineTo(491.085175f, 493.880310f); +path.lineTo(441.082214f, 427.053101f); +//path.lineTo(591.091064f, 627.534851f); path.close(); #endif -path.moveTo(317.093445, 592.013306); -path.lineTo(366.316162, 542.986572); -path.lineTo(416.051514, 486.978577); -path.lineTo(465.786865, 430.970581); -//path.lineTo(317.093445, 592.013306); +path.moveTo(317.093445f, 592.013306f); +path.lineTo(366.316162f, 542.986572f); +path.lineTo(416.051514f, 486.978577f); +path.lineTo(465.786865f, 430.970581f); +//path.lineTo(317.093445f, 592.013306f); path.close(); #if 0 -path.moveTo(289.392517, 517.138489); -path.lineTo(249.886078, 508.598022); -path.lineTo(217.110916, 450.916443); -path.lineTo(196.621033, 394.917633); -//path.lineTo(289.392517, 517.138489); +path.moveTo(289.392517f, 517.138489f); +path.lineTo(249.886078f, 508.598022f); +path.lineTo(217.110916f, 450.916443f); +path.lineTo(196.621033f, 394.917633f); +//path.lineTo(289.392517f, 517.138489f); path.close(); #endif simplify(__FUNCTION__, path, true, out); @@ -438,84 +438,84 @@ path.close(); static void testSimplifySkinnyTriangle3() { SkPath path, out; - path.moveTo(591, 627.534851); - path.lineTo(541, 560.707642); - path.lineTo(491, 493.880310); - path.lineTo(441, 427.053101); + path.moveTo(591, 627.534851f); + path.lineTo(541, 560.707642f); + path.lineTo(491, 493.880310f); + path.lineTo(441, 427.053101f); path.close(); - path.moveTo(317, 592.013306); - path.lineTo(366, 542.986572); - path.lineTo(416, 486.978577); - path.lineTo(465, 430.970581); + path.moveTo(317, 592.013306f); + path.lineTo(366, 542.986572f); + path.lineTo(416, 486.978577f); + path.lineTo(465, 430.970581f); path.close(); simplify(__FUNCTION__, path, true, out); } static void testSimplifySkinnyTriangle4() { SkPath path, out; -path.moveTo(572.655212, 614.959961); -path.lineTo(524.618896, 549.339600); -path.lineTo(476.582581, 483.719269); -path.lineTo(428.546265, 418.098938); -path.lineTo(572.655212, 614.959961); +path.moveTo(572.655212f, 614.959961f); +path.lineTo(524.618896f, 549.339600f); +path.lineTo(476.582581f, 483.719269f); +path.lineTo(428.546265f, 418.098938f); +path.lineTo(572.655212f, 614.959961f); path.close(); -path.moveTo(312.166382, 583.723083); -path.lineTo(361.047791, 529.824219); -path.lineTo(409.929230, 475.925354); -path.lineTo(458.810669, 422.026520); -path.lineTo(312.166382, 583.723083); +path.moveTo(312.166382f, 583.723083f); +path.lineTo(361.047791f, 529.824219f); +path.lineTo(409.929230f, 475.925354f); +path.lineTo(458.810669f, 422.026520f); +path.lineTo(312.166382f, 583.723083f); path.close(); -path.moveTo(278.742737, 508.065643); -path.lineTo(241.475800, 493.465118); -path.lineTo(210.344177, 437.315125); -path.lineTo(197.019455, 383.794556); -path.lineTo(278.742737, 508.065643); +path.moveTo(278.742737f, 508.065643f); +path.lineTo(241.475800f, 493.465118f); +path.lineTo(210.344177f, 437.315125f); +path.lineTo(197.019455f, 383.794556f); +path.lineTo(278.742737f, 508.065643f); path.close(); simplify(__FUNCTION__, path, true, out); } static void testSimplifySkinnyTriangle5() { SkPath path, out; -path.moveTo(554.690613, 602.286072); -path.lineTo(508.590057, 537.906250); -path.lineTo(462.489441, 473.526520); -path.lineTo(416.388855, 409.146729); -path.lineTo(554.690613, 602.286072); +path.moveTo(554.690613f, 602.286072f); +path.lineTo(508.590057f, 537.906250f); +path.lineTo(462.489441f, 473.526520f); +path.lineTo(416.388855f, 409.146729f); +path.lineTo(554.690613f, 602.286072f); path.close(); -path.moveTo(307.216949, 575.189270); -path.lineTo(355.826965, 516.804688); -path.lineTo(403.815918, 464.990753); -path.lineTo(451.804871, 413.176819); -path.lineTo(307.216949, 575.189270); +path.moveTo(307.216949f, 575.189270f); +path.lineTo(355.826965f, 516.804688f); +path.lineTo(403.815918f, 464.990753f); +path.lineTo(451.804871f, 413.176819f); +path.lineTo(307.216949f, 575.189270f); path.close(); -path.moveTo(271.998901, 521.301025); -path.lineTo(234.619705, 499.687683); -path.lineTo(203.059692, 441.332336); -path.lineTo(195.994370, 386.856506); -path.lineTo(271.998901, 521.301025); +path.moveTo(271.998901f, 521.301025f); +path.lineTo(234.619705f, 499.687683f); +path.lineTo(203.059692f, 441.332336f); +path.lineTo(195.994370f, 386.856506f); +path.lineTo(271.998901f, 521.301025f); path.close(); simplify(__FUNCTION__, path, true, out); } static void testSimplifySkinnyTriangle6() { SkPath path, out; -path.moveTo(591.091064, 627.534851); -path.lineTo(541.088135, 560.707642); -path.lineTo(491.085175, 493.880310); -path.lineTo(441.082214, 427.053101); -path.lineTo(591.091064, 627.534851); +path.moveTo(591.091064f, 627.534851f); +path.lineTo(541.088135f, 560.707642f); +path.lineTo(491.085175f, 493.880310f); +path.lineTo(441.082214f, 427.053101f); +path.lineTo(591.091064f, 627.534851f); path.close(); -path.moveTo(317.093445, 592.013306); -path.lineTo(366.316162, 542.986572); -path.lineTo(416.051514, 486.978577); -path.lineTo(465.786865, 430.970581); -path.lineTo(317.093445, 592.013306); +path.moveTo(317.093445f, 592.013306f); +path.lineTo(366.316162f, 542.986572f); +path.lineTo(416.051514f, 486.978577f); +path.lineTo(465.786865f, 430.970581f); +path.lineTo(317.093445f, 592.013306f); path.close(); -path.moveTo(289.392517, 517.138489); -path.lineTo(249.886078, 508.598022); -path.lineTo(217.110916, 450.916443); -path.lineTo(196.621033, 394.917633); -path.lineTo(289.392517, 517.138489); +path.moveTo(289.392517f, 517.138489f); +path.lineTo(249.886078f, 508.598022f); +path.lineTo(217.110916f, 450.916443f); +path.lineTo(196.621033f, 394.917633f); +path.lineTo(289.392517f, 517.138489f); path.close(); simplify(__FUNCTION__, path, true, out); } @@ -561,69 +561,69 @@ static void testSimplifyTriangle24() { static void testSimplifySkinnyTriangle7() { SkPath path, out; -path.moveTo(487.502319, 550.811279); -path.lineTo(448.826050, 491.720123); -path.lineTo(410.149780, 432.628967); -path.lineTo(371.473572, 373.537781); -path.lineTo(487.502319, 550.811279); +path.moveTo(487.502319f, 550.811279f); +path.lineTo(448.826050f, 491.720123f); +path.lineTo(410.149780f, 432.628967f); +path.lineTo(371.473572f, 373.537781f); +path.lineTo(487.502319f, 550.811279f); path.close(); -path.moveTo(295.817108, 532.655579); -path.lineTo(342.896271, 485.912292); -path.lineTo(389.975433, 439.169006); -path.lineTo(437.054596, 392.425781); -path.lineTo(295.817108, 532.655579); +path.moveTo(295.817108f, 532.655579f); +path.lineTo(342.896271f, 485.912292f); +path.lineTo(389.975433f, 439.169006f); +path.lineTo(437.054596f, 392.425781f); +path.lineTo(295.817108f, 532.655579f); path.close(); -path.moveTo(239.726822, 575.025269); -path.lineTo(204.117569, 521.429688); -path.lineTo(171.275452, 454.110382); -path.lineTo(193.328583, 397.859497); -path.lineTo(239.726822, 575.025269); +path.moveTo(239.726822f, 575.025269f); +path.lineTo(204.117569f, 521.429688f); +path.lineTo(171.275452f, 454.110382f); +path.lineTo(193.328583f, 397.859497f); +path.lineTo(239.726822f, 575.025269f); path.close(); simplify(__FUNCTION__, path, true, out); } static void testSimplifySkinnyTriangle8() { SkPath path, out; -path.moveTo(441.943115, 511.678040); -path.lineTo(408.487549, 456.880920); -path.lineTo(375.031952, 402.083801); -path.lineTo(341.576385, 347.286682); -path.lineTo(441.943115, 511.678040); +path.moveTo(441.943115f, 511.678040f); +path.lineTo(408.487549f, 456.880920f); +path.lineTo(375.031952f, 402.083801f); +path.lineTo(341.576385f, 347.286682f); +path.lineTo(441.943115f, 511.678040f); path.close(); -path.moveTo(297.548492, 557.246704); -path.lineTo(350.768494, 507.627014); -path.lineTo(403.988525, 458.007385); -path.lineTo(457.208527, 408.387695); -path.lineTo(297.548492, 557.246704); +path.moveTo(297.548492f, 557.246704f); +path.lineTo(350.768494f, 507.627014f); +path.lineTo(403.988525f, 458.007385f); +path.lineTo(457.208527f, 408.387695f); +path.lineTo(297.548492f, 557.246704f); path.close(); -path.moveTo(209.857895, 615.802979); -path.lineTo(178.249481, 534.230347); -path.lineTo(144.905640, 460.056824); -path.lineTo(192.953125, 404.972900); -path.lineTo(209.857895, 615.802979); +path.moveTo(209.857895f, 615.802979f); +path.lineTo(178.249481f, 534.230347f); +path.lineTo(144.905640f, 460.056824f); +path.lineTo(192.953125f, 404.972900f); +path.lineTo(209.857895f, 615.802979f); path.close(); simplify(__FUNCTION__, path, true, out); } static void testSimplifySkinnyTriangle9() { SkPath path, out; -path.moveTo(439.867065, 528.291931); -path.lineTo(405.413025, 469.107178); -path.lineTo(370.958954, 409.922363); -path.lineTo(336.504883, 350.737610); -path.lineTo(439.867065, 528.291931); +path.moveTo(439.867065f, 528.291931f); +path.lineTo(405.413025f, 469.107178f); +path.lineTo(370.958954f, 409.922363f); +path.lineTo(336.504883f, 350.737610f); +path.lineTo(439.867065f, 528.291931f); path.close(); -path.moveTo(298.922455, 573.251953); -path.lineTo(356.360962, 521.905090); -path.lineTo(413.799438, 470.558228); -path.lineTo(471.237915, 419.211365); -path.lineTo(298.922455, 573.251953); +path.moveTo(298.922455f, 573.251953f); +path.lineTo(356.360962f, 521.905090f); +path.lineTo(413.799438f, 470.558228f); +path.lineTo(471.237915f, 419.211365f); +path.lineTo(298.922455f, 573.251953f); path.close(); -path.moveTo(187.200775, 643.035156); -path.lineTo(159.713165, 540.993774); -path.lineTo(126.257164, 462.198517); -path.lineTo(193.534012, 409.266235); -path.lineTo(187.200775, 643.035156); +path.moveTo(187.200775f, 643.035156f); +path.lineTo(159.713165f, 540.993774f); +path.lineTo(126.257164f, 462.198517f); +path.lineTo(193.534012f, 409.266235f); +path.lineTo(187.200775f, 643.035156f); path.close(); path.close(); simplify(__FUNCTION__, path, true, out); @@ -632,87 +632,87 @@ path.close(); static void testSimplifySkinnyTriangle10() { SkPath path, out; #if 0 -path.moveTo(99.270325, 239.365234); -path.lineTo(105.967056, 173.361206); -path.lineTo(148.821381, 141.309891); -path.lineTo(159.101013, 189.235138); -path.lineTo(99.270325, 239.365234); +path.moveTo(99.270325f, 239.365234f); +path.lineTo(105.967056f, 173.361206f); +path.lineTo(148.821381f, 141.309891f); +path.lineTo(159.101013f, 189.235138f); +path.lineTo(99.270325f, 239.365234f); path.close(); #endif -path.moveTo(213.673737, 413.292938); -path.lineTo(225.200134, 343.616821); -path.lineTo(236.726532, 273.940704); -path.lineTo(219.386414, 231.373322); -path.lineTo(213.673737, 413.292938); +path.moveTo(213.673737f, 413.292938f); +path.lineTo(225.200134f, 343.616821f); +path.lineTo(236.726532f, 273.940704f); +path.lineTo(219.386414f, 231.373322f); +path.lineTo(213.673737f, 413.292938f); path.close(); -path.moveTo(43.485352, 308.984497); -path.lineTo(122.610657, 305.950134); -path.lineTo(201.735962, 302.915802); -path.lineTo(280.861267, 299.881470); -path.lineTo(43.485352, 308.984497); +path.moveTo(43.485352f, 308.984497f); +path.lineTo(122.610657f, 305.950134f); +path.lineTo(201.735962f, 302.915802f); +path.lineTo(280.861267f, 299.881470f); +path.lineTo(43.485352f, 308.984497f); path.close(); simplify(__FUNCTION__, path, true, out); } static void testSimplifySkinnyTriangle11() { SkPath path, out; -path.moveTo(-177.878387, 265.368988); -path.lineTo(-254.415771, 303.709961); -path.lineTo(-317.465363, 271.325562); -path.lineTo(-374.520386, 207.507660); -path.lineTo(-177.878387, 265.368988); +path.moveTo(-177.878387f, 265.368988f); +path.lineTo(-254.415771f, 303.709961f); +path.lineTo(-317.465363f, 271.325562f); +path.lineTo(-374.520386f, 207.507660f); +path.lineTo(-177.878387f, 265.368988f); path.close(); -path.moveTo(-63.582489, -3.679123); -path.lineTo(-134.496841, 26.434566); -path.lineTo(-205.411209, 56.548256); -path.lineTo(-276.325562, 86.661942); -path.lineTo(-63.582489, -3.679123); +path.moveTo(-63.582489f, -3.679123f); +path.lineTo(-134.496841f, 26.434566f); +path.lineTo(-205.411209f, 56.548256f); +path.lineTo(-276.325562f, 86.661942f); +path.lineTo(-63.582489f, -3.679123f); path.close(); -path.moveTo(-57.078423, 162.633453); -path.lineTo(-95.963928, 106.261139); -path.lineTo(-134.849457, 49.888824); -path.lineTo(-173.734955, -6.483480); -path.lineTo(-57.078423, 162.633453); +path.moveTo(-57.078423f, 162.633453f); +path.lineTo(-95.963928f, 106.261139f); +path.lineTo(-134.849457f, 49.888824f); +path.lineTo(-173.734955f, -6.483480f); +path.lineTo(-57.078423f, 162.633453f); path.close(); simplify(__FUNCTION__, path, true, out); } static void testSimplifySkinnyTriangle12() { SkPath path, out; -path.moveTo(98.666489, -94.295059); -path.lineTo(156.584320, -61.939133); -path.lineTo(174.672974, -12.343765); -path.lineTo(158.622345, 52.028267); -path.lineTo(98.666489, -94.295059); +path.moveTo(98.666489f, -94.295059f); +path.lineTo(156.584320f, -61.939133f); +path.lineTo(174.672974f, -12.343765f); +path.lineTo(158.622345f, 52.028267f); +path.lineTo(98.666489f, -94.295059f); path.close(); -path.moveTo(-133.225616, -48.622055); -path.lineTo(-73.855499, -10.375397); -path.lineTo(-14.485367, 27.871277); -path.lineTo(44.884750, 66.117935); -path.lineTo(-133.225616, -48.622055); +path.moveTo(-133.225616f, -48.622055f); +path.lineTo(-73.855499f, -10.375397f); +path.lineTo(-14.485367f, 27.871277f); +path.lineTo(44.884750f, 66.117935f); +path.lineTo(-133.225616f, -48.622055f); path.close(); -path.moveTo( 9.030045, -163.413132); -path.lineTo(-19.605331, -89.588760); -path.lineTo(-48.240707, -15.764404); -path.lineTo(-76.876053, 58.059944); -path.lineTo( 9.030045, -163.413132); +path.moveTo( 9.030045f, -163.413132f); +path.lineTo(-19.605331f, -89.588760f); +path.lineTo(-48.240707f, -15.764404f); +path.lineTo(-76.876053f, 58.059944f); +path.lineTo( 9.030045f, -163.413132f); path.close(); simplify(__FUNCTION__, path, true, out); } static void testSimplifySkinnyTriangle13() { SkPath path, out; -path.moveTo(340.41568, -170.97171); -path.lineTo(418.846893, -142.428329); -path.lineTo(497.278107, -113.884933); -path.lineTo(449.18222, -45.6723022); -path.lineTo(340.41568, -170.97171); +path.moveTo(340.41568f, -170.97171f); +path.lineTo(418.846893f, -142.428329f); +path.lineTo(497.278107f, -113.884933f); +path.lineTo(449.18222f, -45.6723022f); +path.lineTo(340.41568f, -170.97171f); path.close(); -path.moveTo(326.610535, 34.0393639); -path.lineTo(371.334595, -14.9620667); -path.lineTo(416.058624, -63.9634857); -path.lineTo(460.782654, -112.96492); -path.lineTo(326.610535, 34.0393639); +path.moveTo(326.610535f, 34.0393639f); +path.lineTo(371.334595f, -14.9620667f); +path.lineTo(416.058624f, -63.9634857f); +path.lineTo(460.782654f, -112.96492f); +path.lineTo(326.610535f, 34.0393639f); path.close(); simplify(__FUNCTION__, path, true, out); } diff --git a/experimental/Intersection/Intersection_Tests.cpp b/experimental/Intersection/Intersection_Tests.cpp index e3c042cf2..4e4c8de90 100644 --- a/experimental/Intersection/Intersection_Tests.cpp +++ b/experimental/Intersection/Intersection_Tests.cpp @@ -7,8 +7,13 @@ void testSimplify(); #define TEST_QUADS_FIRST 0 void Intersection_Tests() { + SimplifyFindTop_Test(); + SimplifyAngle_Test(); + QuadraticReduceOrder_Test(); + QuadraticBezierClip_Test(); + QuadraticIntersection_Test(); SimplifyAddIntersectingTs_Test(); - + cubecode_test(1); convert_testx(); // tests are in dependency / complexity order @@ -39,8 +44,6 @@ void Intersection_Tests() { #endif QuadraticCoincidence_Test(); - QuadraticReduceOrder_Test(); - QuadraticBezierClip_Test(); QuadraticIntersection_Test(); CubicParameterization_Test(); diff --git a/experimental/Intersection/Intersection_Tests.h b/experimental/Intersection/Intersection_Tests.h index a2d9df680..50e560041 100644 --- a/experimental/Intersection/Intersection_Tests.h +++ b/experimental/Intersection/Intersection_Tests.h @@ -1,3 +1,7 @@ +#if !defined(IN_TEST) + #define IN_TEST 1 +#endif + void ActiveEdge_Test(); void ConvexHull_Test(); void ConvexHull_X_Test(); @@ -13,6 +17,9 @@ void LineIntersection_Test(); void LineParameter_Test(); void LineQuadraticIntersection_Test(); void SimplifyAddIntersectingTs_Test(); +void SimplifyAngle_Test(); +void SimplifyFindNext_Test(); +void SimplifyFindTop_Test(); void SimplifyDegenerate4x4TrianglesThreaded_Test(); void SimplifyNondegenerate4x4TrianglesThreaded_Test(); void SimplifyPolygonPaths_Test(); diff --git a/experimental/Intersection/LineParameters.h b/experimental/Intersection/LineParameters.h index 188962263..202ba46c4 100644 --- a/experimental/Intersection/LineParameters.h +++ b/experimental/Intersection/LineParameters.h @@ -4,6 +4,15 @@ // computer-aided design - volume 22 number 9 november 1990 pp 538 - 549 // online at http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf +// This turns a line segment into a parameterized line, of the form +// ax + by + c = 0 +// When a^2 + b^2 == 1, the line is normalized. +// The distance to the line for (x, y) is d(x,y) = ax + by + c +// +// Note that the distances below are not necessarily normalized. To get the true +// distance, it's necessary to either call normalize() after xxxEndPoints(), or +// divide the result of xxxDistance() by sqrt(normalSquared()) + class LineParameters { public: void cubicEndPoints(const Cubic& pts) { @@ -42,7 +51,7 @@ public: bool normalize() { double normal = sqrt(normalSquared()); - if (normal < SquaredEpsilon) { + if (approximately_zero_squared(normal)) { a = b = c = 0; return false; } @@ -87,6 +96,7 @@ public: double pointDistance(const _Point& pt) { return a * pt.x + b * pt.y + c; } + private: double a; double b; diff --git a/experimental/Intersection/QuadraticBezierClip_Test.cpp b/experimental/Intersection/QuadraticBezierClip_Test.cpp index a9561fd25..289ebdb58 100644 --- a/experimental/Intersection/QuadraticBezierClip_Test.cpp +++ b/experimental/Intersection/QuadraticBezierClip_Test.cpp @@ -2,7 +2,24 @@ #include "Intersection_Tests.h" #include "QuadraticIntersection_TestData.h" -void QuadraticBezierClip_Test() { +static const Quadratic testSet[] = { + {{8.0000000000000071, 8.0000000000000071}, + {8.7289570079366854, 8.7289570079366889}, + {9.3914917259458743, 9.0593802763083691}}, + {{8.0000000000000142, 8.0000000000000142}, + {8.1250000000000107, 8.1250000000000071}, + {8.2500000000000071, 8.2187500000000053}} +}; + +static void oneOffTest() { + const Quadratic& quad1 = testSet[0]; + const Quadratic& quad2 = testSet[1]; + double minT = 0; + double maxT = 1; + bezier_clip(quad1, quad2, minT, maxT); +} + +void standardTestCases() { for (size_t index = 0; index < quadraticTests_count; ++index) { const Quadratic& quad1 = quadraticTests[index][0]; const Quadratic& quad2 = quadraticTests[index][1]; @@ -22,3 +39,8 @@ void QuadraticBezierClip_Test() { } } } + +void QuadraticBezierClip_Test() { + oneOffTest(); + standardTestCases(); +} diff --git a/experimental/Intersection/QuadraticIntersection_Test.cpp b/experimental/Intersection/QuadraticIntersection_Test.cpp index 63b733830..582b19563 100644 --- a/experimental/Intersection/QuadraticIntersection_Test.cpp +++ b/experimental/Intersection/QuadraticIntersection_Test.cpp @@ -3,10 +3,11 @@ #include "Intersections.h" #include "QuadraticIntersection_TestData.h" #include "TestUtilities.h" +#include "SkTypes.h" const int firstQuadIntersectionTest = 9; -void QuadraticIntersection_Test() { +static void standardTestCases() { for (size_t index = firstQuadIntersectionTest; index < quadraticTests_count; ++index) { const Quadratic& quad1 = quadraticTests[index][0]; const Quadratic& quad2 = quadraticTests[index][1]; @@ -44,3 +45,49 @@ void QuadraticIntersection_Test() { } } +static const Quadratic testSet[] = { + {{8, 8}, {10, 10}, {8, -10}}, + {{8, 8}, {12, 12}, {14, 4}}, + {{8, 8}, {9, 9}, {10, 8}} +}; + +const size_t testSetCount = sizeof(testSet) / sizeof(testSet[0]); + +static void oneOffTest() { + for (int outer = 0; outer < testSetCount - 1; ++outer) { + for (int inner = outer + 1; inner < testSetCount; ++inner) { + const Quadratic& quad1 = testSet[outer]; + const Quadratic& quad2 = testSet[inner]; + Intersections intersections; + intersect(quad1, quad2, intersections); + if (!intersections.intersected()) { + SkDebugf("%s no intersection!\n", __FUNCTION__); + } + for (int pt = 0; pt < intersections.used(); ++pt) { + double tt1 = intersections.fT[0][pt]; + double tx1, ty1; + xy_at_t(quad1, tt1, tx1, ty1); + double tt2 = intersections.fT[1][pt]; + double tx2, ty2; + xy_at_t(quad2, tt2, tx2, ty2); + if (!approximately_equal(tx1, tx2)) { + SkDebugf("%s [%d,%d] x!= t1=%g (%g,%g) t2=%g (%g,%g)\n", + __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); + SkASSERT(0); + } + if (!approximately_equal(ty1, ty2)) { + SkDebugf("%s [%d,%d] y!= t1=%g (%g,%g) t2=%g (%g,%g)\n", + __FUNCTION__, (int)index, pt, tt1, tx1, ty1, tt2, tx2, ty2); + SkASSERT(0); + } + SkDebugf("%s [%d][%d] t1=%1.9g (%1.9g, %1.9g) t2=%1.9g\n", __FUNCTION__, + outer, inner, tt1, tx1, tx2, tt2); + } + } + } +} + +void QuadraticIntersection_Test() { + oneOffTest(); + standardTestCases(); +} \ No newline at end of file diff --git a/experimental/Intersection/QuadraticIntersection_TestData.cpp b/experimental/Intersection/QuadraticIntersection_TestData.cpp index 2fa8a98a9..9ec585a73 100644 --- a/experimental/Intersection/QuadraticIntersection_TestData.cpp +++ b/experimental/Intersection/QuadraticIntersection_TestData.cpp @@ -1,10 +1,8 @@ /* - * QuadraticIntersection_TestData.cpp - * edge - * - * Created by Cary Clark on 1/10/12. - * Copyright 2012 __MyCompanyName__. All rights reserved. + * 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 "QuadraticIntersection_TestData.h" diff --git a/experimental/Intersection/QuadraticIntersection_TestData.h b/experimental/Intersection/QuadraticIntersection_TestData.h index 54f084cff..1852d1709 100644 --- a/experimental/Intersection/QuadraticIntersection_TestData.h +++ b/experimental/Intersection/QuadraticIntersection_TestData.h @@ -1,3 +1,7 @@ +#if !defined(IN_TEST) + #define IN_TEST 1 +#endif + #include "DataTypes.h" extern const Quadratic quadraticLines[]; diff --git a/experimental/Intersection/QuadraticReduceOrder.cpp b/experimental/Intersection/QuadraticReduceOrder.cpp index f85cdc86e..7429f6eda 100644 --- a/experimental/Intersection/QuadraticReduceOrder.cpp +++ b/experimental/Intersection/QuadraticReduceOrder.cpp @@ -110,11 +110,10 @@ static int check_linear(const Quadratic& quad, Quadratic& reduction, bool isLinear(const Quadratic& quad, int startIndex, int endIndex) { LineParameters lineParameters; lineParameters.quadEndPoints(quad, startIndex, endIndex); - double normalSquared = lineParameters.normalSquared(); - double distance = lineParameters.controlPtDistance(quad); // not normalized - double limit = normalSquared * SquaredEpsilon; - double distSq = distance * distance; - return distSq <= limit; + // FIXME: maybe it's possible to avoid this and compare non-normalized + lineParameters.normalize(); + double distance = lineParameters.controlPtDistance(quad); + return approximately_zero(distance); } // reduce to a quadratic or smaller diff --git a/experimental/Intersection/QuadraticReduceOrder_Test.cpp b/experimental/Intersection/QuadraticReduceOrder_Test.cpp index 397342b9a..b716cdb1a 100644 --- a/experimental/Intersection/QuadraticReduceOrder_Test.cpp +++ b/experimental/Intersection/QuadraticReduceOrder_Test.cpp @@ -2,8 +2,27 @@ #include "Intersection_Tests.h" #include "QuadraticIntersection_TestData.h" #include "TestUtilities.h" +#include "SkTypes.h" -void QuadraticReduceOrder_Test() { +static const Quadratic testSet[] = { + {{1, 1}, {2, 2}, {1, 1.000003}}, + {{1, 0}, {2, 6}, {3, 0}} +}; + +static const size_t testSetCount = sizeof(testSet) / sizeof(testSet[0]); + + +static void oneOffTest() { + SkDebugf("%s FLT_EPSILON=%1.9g\n", __FUNCTION__, FLT_EPSILON); + for (int index = 0; index < testSetCount; ++index) { + const Quadratic& quad = testSet[index]; + Quadratic reduce; + int order = reduceOrder(quad, reduce); + SkASSERT(order == 3); + } +} + +static void standardTestCases() { size_t index; Quadratic reduce; int order; @@ -36,3 +55,8 @@ void QuadraticReduceOrder_Test() { } } } + +void QuadraticReduceOrder_Test() { + oneOffTest(); + standardTestCases(); +} diff --git a/experimental/Intersection/Simplify.cpp b/experimental/Intersection/Simplify.cpp index cb2e3bc42..444b32d8d 100644 --- a/experimental/Intersection/Simplify.cpp +++ b/experimental/Intersection/Simplify.cpp @@ -4,16 +4,7 @@ * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ -#include "CurveIntersection.h" -#include "Intersections.h" -#include "LineIntersection.h" -#include "SkPath.h" -#include "SkRect.h" -#include "SkTArray.h" -#include "SkTDArray.h" -#include "ShapeOps.h" -#include "TSearch.h" -#include // used for std::min +#include "Simplify.h" #undef SkASSERT #define SkASSERT(cond) while (!(cond)) { sk_throw(); } @@ -21,8 +12,8 @@ // Terminology: // A Path contains one of more Contours // A Contour is made up of Segment array -// A Segment is described by a Verb and a Point array -// A Verb is one of Line, Quad(ratic), and Cubic +// A Segment is described by a Verb and a Point array with 2, 3, or 4 points +// A Verb is one of Line, Quad(ratic), or Cubic // A Segment contains a Span array // A Span is describes a portion of a Segment using starting and ending T // T values range from 0 to 1, where 0 is the first Point in the Segment @@ -263,6 +254,14 @@ static void CubicSubDivide(const SkPoint a[4], double startT, double endT, sub[3].fY = SkDoubleToScalar(dst[3].y); } +static void (* const SegmentSubDivide[])(const SkPoint [], double , double , + SkPoint []) = { + NULL, + LineSubDivide, + QuadSubDivide, + CubicSubDivide +}; + static void QuadSubBounds(const SkPoint a[3], double startT, double endT, SkRect& bounds) { SkPoint dst[3]; @@ -291,6 +290,9 @@ static SkPath::Verb QuadReduceOrder(const SkPoint a[3], {a[2].fX, a[2].fY}}; Quadratic dst; int order = reduceOrder(aQuad, dst); + if (order == 3) { + return SkPath::kQuad_Verb; + } for (int index = 0; index < order; ++index) { SkPoint* pt = reducePts.append(); pt->fX = SkDoubleToScalar(dst[index].x); @@ -305,6 +307,9 @@ static SkPath::Verb CubicReduceOrder(const SkPoint a[4], {a[2].fX, a[2].fY}, {a[3].fX, a[3].fY}}; Cubic dst; int order = reduceOrder(aCubic, dst, kReduceOrder_QuadraticsAllowed); + if (order == 4) { + return SkPath::kCubic_Verb; + } for (int index = 0; index < order; ++index) { SkPoint* pt = reducePts.append(); pt->fX = SkDoubleToScalar(dst[index].x); @@ -330,19 +335,19 @@ static SkScalar LineLeftMost(const SkPoint a[2], double startT, double endT) { double x[2]; xy_at_t(aLine, startT, x[0], *(double*) 0); xy_at_t(aLine, endT, x[0], *(double*) 0); - return startT < endT ? startT : endT; + return startT < endT ? (float) startT : (float) endT; } static SkScalar QuadLeftMost(const SkPoint a[3], double startT, double endT) { const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}}; - return leftMostT(aQuad, startT, endT); + return (float) leftMostT(aQuad, startT, endT); } static SkScalar CubicLeftMost(const SkPoint a[4], double startT, double endT) { const Cubic aCubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}, {a[3].fX, a[3].fY}}; - return leftMostT(aCubic, startT, endT); + return (float) leftMostT(aCubic, startT, endT); } static SkScalar (* const SegmentLeftMost[])(const SkPoint [], double , double) = { @@ -359,57 +364,166 @@ static bool IsCoincident(const SkPoint a[2], const SkPoint& above, return implicit_matches_ulps(aLine, bLine, 32); } +class Segment; + // sorting angles // given angles of {dx dy ddx ddy dddx dddy} sort them class Angle { public: + // 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. bool operator<(const Angle& rh) const { - if ((dy < 0) ^ (rh.dy < 0)) { - return dy < 0; + if ((fDy < 0) ^ (rh.fDy < 0)) { + return fDy < 0; } - SkScalar cmp = dx * rh.dy - rh.dx * dy; + if (fDy == 0 && rh.fDy == 0 && fDx != rh.fDx) { + return fDx < rh.fDx; + } + SkScalar cmp = fDx * rh.fDy - rh.fDx * fDy; if (cmp) { return cmp < 0; } - if ((ddy < 0) ^ (rh.ddy < 0)) { - return ddy < 0; + if ((fDDy < 0) ^ (rh.fDDy < 0)) { + return fDDy < 0; } - cmp = ddx * rh.ddy - rh.ddx * ddy; + if (fDDy == 0 && rh.fDDy == 0 && fDDx != rh.fDDx) { + return fDDx < rh.fDDx; + } + cmp = fDDx * rh.fDDy - rh.fDDx * fDDy; if (cmp) { return cmp < 0; } - if ((dddy < 0) ^ (rh.dddy < 0)) { - return ddy < 0; + if ((fDDDy < 0) ^ (rh.fDDDy < 0)) { + return fDDDy < 0; } - return dddx * rh.dddy < rh.dddx * dddy; + if (fDDDy == 0 && rh.fDDDy == 0) { + return fDDDx < rh.fDDDx; + } + return fDDDx * rh.fDDDy < rh.fDDDx * fDDDy; } - void set(SkPoint* pts, SkPath::Verb verb) { - dx = pts[1].fX - pts[0].fX; // b - a - dy = pts[1].fY - pts[0].fY; + int end() const { + return fEnd; + } + + // since all angles share a point, this needs to know which point + // is the common origin, i.e., whether the center is at pts[0] or pts[verb] + // practically, this should only be called by addAngle + void set(const SkPoint* pts, SkPath::Verb verb, const Segment* segment, + int start, int end, bool coincident) { + SkASSERT(start != end); + fSegment = segment; + fStart = start; + fEnd = end; + fCoincident = coincident; + fDx = pts[1].fX - pts[0].fX; // b - a + fDy = pts[1].fY - pts[0].fY; if (verb == SkPath::kLine_Verb) { - ddx = ddy = dddx = dddy = 0; + fDDx = fDDy = fDDDx = fDDDy = 0; return; } - ddx = pts[2].fX - pts[1].fX - dx; // a - 2b + c - ddy = pts[2].fY - pts[2].fY - dy; + fDDx = pts[2].fX - pts[1].fX - fDx; // a - 2b + c + fDDy = pts[2].fY - pts[1].fY - fDy; if (verb == SkPath::kQuad_Verb) { - dddx = dddy = 0; + fDDDx = fDDDy = 0; return; } - dddx = pts[3].fX + 3 * (pts[1].fX - pts[2].fX) - pts[0].fX; - dddy = pts[3].fY + 3 * (pts[1].fY - pts[2].fY) - pts[0].fY; + fDDDx = pts[3].fX + 3 * (pts[1].fX - pts[2].fX) - pts[0].fX; + fDDDy = pts[3].fY + 3 * (pts[1].fY - pts[2].fY) - pts[0].fY; + } + + // noncoincident quads/cubics may have the same initial angle + // as lines, so must sort by derivatives as well + // if flatness turns out to be a reasonable way to sort, use the below: + void setFlat(const SkPoint* pts, SkPath::Verb verb, const Segment* segment, + int start, int end, bool coincident) { + fSegment = segment; + fStart = start; + fEnd = end; + fCoincident = coincident; + fDx = pts[1].fX - pts[0].fX; // b - a + fDy = pts[1].fY - pts[0].fY; + if (verb == SkPath::kLine_Verb) { + fDDx = fDDy = fDDDx = fDDDy = 0; + return; + } + if (verb == SkPath::kQuad_Verb) { + int uplsX = FloatAsInt(pts[2].fX - pts[1].fY - fDx); + int uplsY = FloatAsInt(pts[2].fY - pts[1].fY - fDy); + int larger = std::max(abs(uplsX), abs(uplsY)); + int shift = 0; + double flatT; + SkPoint ddPt; // FIXME: get rid of copy (change fDD_ to point) + LineParameters implicitLine; + _Line tangent = {{pts[0].fX, pts[0].fY}, {pts[1].fX, pts[1].fY}}; + implicitLine.lineEndPoints(tangent); + implicitLine.normalize(); + while (larger > UlpsEpsilon * 1024) { + larger >>= 2; + ++shift; + flatT = 0.5 / (1 << shift); + QuadXYAtT(pts, flatT, &ddPt); + _Point _pt = {ddPt.fX, ddPt.fY}; + double distance = implicitLine.pointDistance(_pt); + if (approximately_zero(distance)) { + SkDebugf("%s ulps too small %1.9g\n", __FUNCTION__, distance); + break; + } + } + flatT = 0.5 / (1 << shift); + QuadXYAtT(pts, flatT, &ddPt); + fDDx = ddPt.fX - pts[0].fX; + fDDy = ddPt.fY - pts[0].fY; + SkASSERT(fDDx != 0 || fDDy != 0); + fDDDx = fDDDy = 0; + return; + } + SkASSERT(0); // FIXME: add cubic case + } + + const Segment* segment() const { + return fSegment; + } + + int sign() const { + int result = fStart - fEnd >> 31 | 1; + SkASSERT(result == fStart < fEnd ? -1 : 1); + return result; + } + + int start() const { + return fStart; } private: - SkScalar dx; - SkScalar dy; - SkScalar ddx; - SkScalar ddy; - SkScalar dddx; - SkScalar dddy; + SkScalar fDx; + SkScalar fDy; + SkScalar fDDx; + SkScalar fDDy; + SkScalar fDDDx; + SkScalar fDDDy; + const Segment* fSegment; + int fStart; + int fEnd; + bool fCoincident; }; +static void sortAngles(SkTDArray& angles, SkTDArray& angleList) { + int angleCount = angles.count(); + int angleIndex; + angleList.setReserve(angleCount); + for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) { + *angleList.append() = &angles[angleIndex]; + } + QSort(angleList.begin(), angleList.end() - 1); +} + // Bounds, unlike Rect, does not consider a vertical line to be empty. struct Bounds : public SkRect { static bool Intersects(const Bounds& a, const Bounds& b) { @@ -429,7 +543,8 @@ struct Bounds : public SkRect { Cubic cubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}, {a[3].fX, a[3].fY}}; dRect.setBounds(cubic); - set(dRect.left, dRect.top, dRect.right, dRect.bottom); + set((float) dRect.left, (float) dRect.top, (float) dRect.right, + (float) dRect.bottom); } void setQuadBounds(const SkPoint a[3]) { @@ -437,16 +552,16 @@ struct Bounds : public SkRect { {a[2].fX, a[2].fY}}; _Rect dRect; dRect.setBounds(quad); - set(dRect.left, dRect.top, dRect.right, dRect.bottom); + set((float) dRect.left, (float) dRect.top, (float) dRect.right, + (float) dRect.bottom); } }; -class Segment; - struct Span { double fT; Segment* fOther; - double fOtherT; + double fOtherT; // value at fOther[fOtherIndex].fT + int fOtherIndex; // can't be used during intersection int fWinding; // accumulated from contours surrounding this one // OPTIMIZATION: done needs only 2 bits (values are -1, 0, 1) int fDone; // set when t to t+fDone is processed @@ -462,31 +577,34 @@ public: #endif } - void addAngle(SkTDArray& angles, double start, double end) { - // FIXME complete this - // start here; + void addAngle(SkTDArray& angles, int start, int end, + bool coincident) const { + SkASSERT(start != end); + SkPoint edge[4]; + (*SegmentSubDivide[fVerb])(fPts, fTs[start].fT, fTs[end].fT, edge); + Angle* angle = angles.append(); + angle->set(edge, fVerb, this, start, end, coincident); } - bool addCubic(const SkPoint pts[4]) { - fPts = pts; - fVerb = SkPath::kCubic_Verb; + void addCubic(const SkPoint pts[4]) { + init(pts, SkPath::kCubic_Verb); fBounds.setCubicBounds(pts); } - bool addLine(const SkPoint pts[2]) { - fPts = pts; - fVerb = SkPath::kLine_Verb; + void addLine(const SkPoint pts[2]) { + init(pts, SkPath::kLine_Verb); fBounds.set(pts, 2); } // add 2 to edge or out of range values to get T extremes - void addOtherT(int index, double other) { - fTs[index].fOtherT = other; + void addOtherT(int index, double otherT, int otherIndex) { + Span& span = fTs[index]; + span.fOtherT = otherT; + span.fOtherIndex = otherIndex; } - bool addQuad(const SkPoint pts[3]) { - fPts = pts; - fVerb = SkPath::kQuad_Verb; + void addQuad(const SkPoint pts[3]) { + init(pts, SkPath::kQuad_Verb); fBounds.setQuadBounds(pts); } @@ -522,10 +640,70 @@ finish: return insertedAt; } + void addTwoAngles(int start, int end, const SkPoint& endLoc, + const Span* endSpan, bool startCo, SkTDArray& angles) const { + // add edge leading into junction + addAngle(angles, end, start, startCo); + // add edge leading away from junction + bool coincident; + int step = start < end ? 1 : -1; + int tIndex = nextSpan(end, step, endLoc, endSpan, NULL, coincident); + if (tIndex >= 0) { + lastSpan(tIndex, step, endLoc, endSpan, coincident); + addAngle(angles, end, tIndex, coincident); + } + } + const Bounds& bounds() const { return fBounds; } + void buildAngles(int index, int last, int step, const SkPoint& loc, + SkTDArray& angles) const { + SkASSERT(index - last != 0); + SkASSERT((index - last < 0) ^ (step < 0)); + int end = last + step; + do { + Span* span = &fTs[index]; + Segment* other = span->fOther; + if (other->fDone) { + continue; + } + // if there is only one live crossing, and no coincidence, continue + // in the same direction + // if there is coincidence, the only choice may be to reverse direction + // find edge on either side of intersection + int oIndex = span->fOtherIndex; + Span* otherSpan = &other->fTs[oIndex]; + SkASSERT(otherSpan->fOther == this); + // if done == -1, prior span has already been processed + bool otherCo; + int localStep = step; + int next = other->nextSpan(oIndex, localStep, loc, otherSpan, + NULL, otherCo); + if (next < 0) { + localStep = -step; + next = other->nextSpan(oIndex, localStep, loc, otherSpan, + NULL, otherCo); + } + other->lastSpan(next, localStep, loc, otherSpan, otherCo); + // add candidate into and away from junction + other->addTwoAngles(next, oIndex, loc, span, otherCo, angles); + + } while ((index += step) != end); + } + + // figure out if the segment's ascending T goes clockwise or not + // not enough context to write this as shown + // instead, add all segments meeting at the top + // sort them using buildAngleList + // find the first in the sort + // see if ascendingT goes to top + bool clockwise(int tIndex) const { + SkASSERT(0); // incomplete + return false; + } + bool done() const { return fDone; } @@ -545,13 +723,13 @@ finish: SkASSERT(0); // should never get here return -1; } - + // start is the index of the beginning T of this edge // it is guaranteed to have an end which describes a non-zero length (?) // winding -1 means ccw, 1 means cw // step is in/out -1 or 1 // spanIndex is returned - Segment* findNext(int start, int winding, int& step, int& spanIndex) { + Segment* findNext(int start, int winding, int& step, int& spanIndex) const { SkASSERT(step == 1 || step == -1); int count = fTs.count(); SkASSERT(step > 0 ? start < count - 1 : start > 0); @@ -565,129 +743,83 @@ finish: SkPoint startLoc; // OPTIMIZATION: store this in the t span? xyAtT(startSpan->fT, &startLoc); SkPoint endLoc; - Span* endSpan; - int end = nextSpan(start, step, startLoc, startSpan, &endLoc, &endSpan); + bool startCo; + int end = nextSpan(start, step, startLoc, startSpan, &endLoc, startCo); // if we hit the end looking for span end, is that always an error? SkASSERT(step > 0 ? end + 1 < count : end - 1 >= 0); // preflight for coincidence -- if present, it may change winding // considerations and whether reversed edges can be followed - bool foundCoincident = false; - int last = lastSpan(end, step, &startLoc, startSpan, foundCoincident); + int last = lastSpan(end, step, startLoc, startSpan, startCo); // Discard opposing direction candidates if no coincidence was found. + Span* endSpan = &fTs[end]; int candidateCount = abs(last - end); + Segment* other; if (candidateCount == 1) { - SkASSERT(!foundCoincident); + SkASSERT(!startCo); // move in winding direction until edge in correct direction // balance wrong direction edges before finding correct one // this requres that the intersection is angularly sorted // for a single intersection, special case -- choose the opposite // edge that steps the same - Segment* other = endSpan->fOther; + other = endSpan->fOther; SkASSERT(!other->fDone); - spanIndex = other->matchSpan(this, endSpan->fT); - SkASSERT(step < 0 ? spanIndex > 0 : spanIndex < other->fTs.count() - 1); + spanIndex = endSpan->fOtherIndex; + SkASSERT(step < 0 ? spanIndex > 0 + : spanIndex < other->fTs.count() - 1); return other; } - // find the next T that describes a length + // more than one viable candidate -- measure angles to find best SkTDArray angles; - Segment* segmentCandidate = NULL; - int spanCandidate = -1; - int directionCandidate; - do { - endSpan = &fTs[end]; - Segment* other = endSpan->fOther; - if (other->fDone) { - continue; + SkASSERT(end - start != 0); + SkASSERT((end - start < 0) ^ (step < 0)); + addTwoAngles(start, end, endLoc, endSpan, startCo, angles); + buildAngles(end, last, step, endLoc, angles); + SkTDArray sorted; + sortAngles(angles, sorted); + // find the starting edge + int startIndex = -1; + int angleCount = angles.count(); + int angleIndex; + const Angle* angle; + for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) { + angle = sorted[angleIndex]; + if (angle->segment() == this && angle->start() == end && + angle->end() == start) { + startIndex = angleIndex; + break; } - // if there is only one live crossing, and no coincidence, continue - // in the same direction - // if there is coincidence, the only choice may be to reverse direction - // find edge on either side of intersection - int oIndex = other->matchSpan(this, endSpan->fT); - int oCount = other->fTs.count(); - do { - Span& otherSpan = other->fTs[oIndex]; - // if done == -1, prior span has already been processed - int next = other->nextSpan(oIndex, step, endLoc, &otherSpan, - NULL, NULL); - if (next < 0) { - continue; - } - bool otherIsCoincident; - last = other->lastSpan(next, step, &endLoc, &otherSpan, - otherIsCoincident); - if (last < 0) { - continue; - } - #if 0 - Span& prior = other->fTs[oIndex - 1]; - if (otherSpan.fDone >= 0 && oIndex > 0) { - // FIXME: this needs to loop on -- until t && pt are different - if (prior.fDone > 0) { - continue; - } - - } - } else { // step == 1 - if (otherSpan.fDone <= 0 && oIndex < oCount - 1) { - // FIXME: this needs to loop on ++ until t && pt are different - Span& next = other->fTs[oIndex + 1]; - if (next.fDone < 0) { - continue; - } - } - } - #endif - if (!segmentCandidate) { - segmentCandidate = other; - spanCandidate = oIndex; - directionCandidate = step; - continue; - } - // there's two or more matches - if (spanCandidate >= 0) { // retrieve first stored candidate - // add edge leading into junction - addAngle(angles, endSpan->fT, startSpan->fT); - // add edge leading away from junction - double nextT = nextSpan(end, step, endLoc, endSpan, NULL, - NULL); - if (nextT >= 0) { - addAngle(angles, endSpan->fT, nextT); - } - // add first stored candidate into junction - segmentCandidate->addAngle(angles, - segmentCandidate->fTs[spanCandidate - 1].fT, - segmentCandidate->fTs[spanCandidate].fT); - // add first stored candidate away from junction - segmentCandidate->addAngle(angles, - segmentCandidate->fTs[spanCandidate].fT, - segmentCandidate->fTs[spanCandidate + 1].fT); - } - // add candidate into and away from junction - - - // start here; - // more than once viable candidate -- need to - // measure angles to find best - // noncoincident quads/cubics may have the same initial angle - // as lines, so must sort by derivatives as well - // while we're here, figure out all connections given the - // initial winding info - // so the span needs to contain the pairing info found here - // this should include the winding computed for the edge, and - // what edge it connects to, and whether it is discarded - // (maybe discarded == abs(winding) > 1) ? - // only need derivatives for duration of sorting, add a new struct - // for pairings, remove extra spans that have zero length and - // reference an unused other - // for coincident, the last span on the other may be marked done - // (always?) - } while (++oIndex < oCount); - } while ((end += step) != last); + } + SkASSERT(startIndex >= 0); + winding += angle->sign(); + int nextIndex = startIndex; + const Angle* nextAngle; + do { + if (++nextIndex == angleCount) { + nextIndex = 0; + } + SkASSERT(nextIndex != startIndex); // should never wrap around + nextAngle = sorted[nextIndex]; + // OPTIMIZATION: Figure out all connections, given the initial + // winding info (e.g., accumulate winding in span for reuse) + winding -= nextAngle->sign(); + } while (winding); + // FIXME: get rid of cast + return const_cast(nextAngle->segment()); + + // so the span needs to contain the pairing info found here + // this should include the winding computed for the edge, and + // what edge it connects to, and whether it is discarded + // (maybe discarded == abs(winding) > 1) ? + // only need derivatives for duration of sorting, add a new struct + // for pairings, remove extra spans that have zero length and + // reference an unused other + // for coincident, the last span on the other may be marked done + // (always?) + // if loop is exhausted, contour may be closed. // FIXME: pass in close point so we can check for closure @@ -760,8 +892,7 @@ finish: } continue; } - if (toSpan.fOther == mOther && toSpan.fOtherT - == moSpan.fT) { + if (toSpan.fOther == mOther && toSpan.fOtherT == moSpan.fT) { found = true; break; } @@ -786,28 +917,15 @@ finish: } } - int findByT(double t, const Segment* match) const { - // OPTIMIZATION: bsearch if count is honkin huge - int count = fTs.count(); - for (int index = 0; index < count; ++index) { - const Span& span = fTs[index]; - if (t == span.fT && match == span.fOther) { - return index; - } - } - SkASSERT(0); // should never get here - return -1; - } - // find the adjacent T that is leftmost, with a point != base int findLefty(int tIndex, const SkPoint& base) const { - int bestTIndex; + int bestTIndex = -1; SkPoint test; - SkScalar bestX = DBL_MAX; + SkScalar bestX = FLT_MAX; int testTIndex = tIndex; while (--testTIndex >= 0) { - xyAtT(testTIndex, &test); - if (test != base) { + xyAtT(fTs[testTIndex].fT, &test); + if (test == base) { continue; } bestX = test.fX; @@ -817,20 +935,23 @@ finish: int count = fTs.count(); testTIndex = tIndex; while (++testTIndex < count) { - xyAtT(testTIndex, &test); + xyAtT(fTs[testTIndex].fT, &test); if (test == base) { continue; } - return bestX > test.fX ? testTIndex : bestTIndex; + if (bestX > test.fX) { + bestTIndex = testTIndex; + } + break; } - SkASSERT(0); // can't get here (?) - return -1; + SkASSERT(bestTIndex != -1); + return bestTIndex; } // OPTIMIZATION : for a pair of lines, can we compute points at T (cached) // and use more concise logic like the old edge walker code? // FIXME: this needs to deal with coincident edges - const Segment* findTop(int& tIndex) const { + const Segment* findTop(int& tIndex, int& direction) const { // iterate through T intersections and return topmost // topmost tangent from y-min to first pt is closer to horizontal int firstT = 0; @@ -841,7 +962,7 @@ finish: for (index = 1; index < count; ++index) { const Span& span = fTs[index]; double t = span.fT; - SkScalar yIntercept = yAtT(t); + SkScalar yIntercept = t == 1 ? fPts[fVerb].fY : yAtT(t); if (topY > yIntercept) { topY = yIntercept; firstT = lastT = index; @@ -850,44 +971,65 @@ finish: } } // if there's only a pair of segments, go with the endpoint chosen above - if (firstT == lastT && (firstT == 0 || firstT == count - 1)) { + if (firstT == lastT) { tIndex = firstT; return this; } - // if the topmost T is not on end, or is three-way or more, find left - SkPoint leftBase; - xyAtT(firstT, &leftBase); - int tLeft = findLefty(firstT, leftBase); - const Segment* leftSegment = this; - // look for left-ness from tLeft to firstT (matching y of other) - for (index = firstT; index <= lastT; ++index) { - const Segment* other = fTs[index].fOther; - double otherT = fTs[index].fOtherT; - int otherTIndex = other->findByT(otherT, this); - // pick companionT closest (but not too close) on either side - int otherTLeft = other->findLefty(otherTIndex, leftBase); - // within this span, find highest y - SkPoint testPt, otherPt; - testPt.fY = yAtT(tLeft); - otherPt.fY = other->yAtT(otherTLeft); - // FIXME: incomplete - // find the y intercept with the opposite segment - if (testPt.fY < otherPt.fY) { - - } else if (testPt.fY > otherPt.fY) { - - } - // FIXME: leftMost no good. Use y intercept instead -#if 0 - SkScalar otherMost = other->leftMost(otherTIndex, otherTLeft); - if (otherMost < left) { - leftSegment = other; - } -#endif + // sort the edges to find the leftmost + SkPoint startLoc; // OPTIMIZATION: store this in the t span? + const Span* startSpan = &fTs[firstT]; + xyAtT(startSpan->fT, &startLoc); + SkPoint endLoc; + bool nextCo; + int end = nextSpan(firstT, 1, startLoc, startSpan, &endLoc, nextCo); + if (end == -1) { + end = nextSpan(firstT, -1, startLoc, startSpan, &endLoc, nextCo); } + // if the topmost T is not on end, or is three-way or more, find left + // look for left-ness from tLeft to firstT (matching y of other) + SkTDArray angles; + SkASSERT(firstT - end != 0); + addTwoAngles(end, firstT, endLoc, &fTs[firstT], nextCo, angles); + buildAngles(firstT, lastT, 1, startLoc, angles); + SkTDArray sorted; + sortAngles(angles, sorted); + const Segment* leftSegment = sorted[0]->segment(); + tIndex = sorted[0]->end(); + direction = sorted[0]->start() - tIndex; + SkASSERT(direction); + direction = direction < 0 ? -1 : 1; return leftSegment; } + // FIXME: not crazy about this + // when the intersections are performed, the other index is into an + // incomplete array. as the array grows, the indices become incorrect + // while the following fixes the indices up again, it isn't smart about + // skipping segments whose indices are already correct + // assuming we leave the code that wrote the index in the first place + void fixOtherTIndex() { + int iCount = fTs.count(); + for (int i = 0; i < iCount; ++i) { + Span& iSpan = fTs[i]; + double oT = iSpan.fOtherT; + Segment* other = iSpan.fOther; + int oCount = other->fTs.count(); + for (int o = 0; o < oCount; ++o) { + Span& oSpan = other->fTs[o]; + if (oT == oSpan.fT && this == oSpan.fOther) { + iSpan.fOtherIndex = o; + } + } + } + } + + void init(const SkPoint pts[], SkPath::Verb verb) { + fPts = pts; + fVerb = verb; + fDone = false; + fCoincident = 0; + } + bool intersected() const { return fTs.count() > 0; } @@ -916,59 +1058,49 @@ finish: return fBounds.fLeft == fBounds.fRight; } - int lastSpan(int end, int step, const SkPoint* startLoc, - const Span* startSpan, bool& coincident) { + int lastSpan(int end, int step, const SkPoint& startLoc, + const Span* startSpan, bool& coincident) const { int last = end; int count = fTs.count(); - coincident = false; SkPoint lastLoc; do { - if (fTs[last].fCoincident == -step) { + end = last; + if (fTs[end].fCoincident == -step) { coincident = true; } - if (step > 0 ? ++last < count : --last >= 0) { - return -1; + if (step > 0 ? ++last >= count : --last < 0) { + return end; } const Span& lastSpan = fTs[last]; if (lastSpan.fDone == -step) { - return -1; + return end; } if (lastSpan.fT == startSpan->fT) { continue; } xyAtT(lastSpan.fT, &lastLoc); - } while (*startLoc == lastLoc); - return last; + } while (startLoc == lastLoc); + return end; } SkScalar leftMost(int start, int end) const { return (*SegmentLeftMost[fVerb])(fPts, fTs[start].fT, fTs[end].fT); } - int matchSpan(const Segment* match, double matchT) - { - int count = fTs.count(); - for (int index = 0; index < count; ++index) { - Span& span = fTs[index]; - if (span.fOther != match) { - continue; - } - if (span.fOtherT != matchT) { - continue; - } - return index; - } - SkASSERT(0); // should never get here - return -1; - } - int nextSpan(int from, int step, const SkPoint& fromLoc, - const Span* fromSpan, SkPoint* toLoc, Span** toSpan) { + const Span* fromSpan, SkPoint* toLoc, bool& coincident) const { + coincident = false; + if (fDone) { + return -1; + } int count = fTs.count(); int to = from; while (step > 0 ? ++to < count : --to >= 0) { Span* span = &fTs[to]; - if (span->fT == fromSpan->fT) { + if (span->fCoincident == step) { + coincident = true; + } + if (fromSpan->fT == span->fT) { continue; } SkPoint loc; @@ -976,12 +1108,12 @@ finish: if (fromLoc == loc) { continue; } + if (span->fDone == -step) { + return -1; + } if (toLoc) { *toLoc = loc; } - if (toSpan) { - *toSpan = span; - } return to; } return -1; @@ -992,32 +1124,36 @@ finish: } void reset() { - fPts = NULL; - fVerb = (SkPath::Verb) -1; + init(NULL, (SkPath::Verb) -1); fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax); fTs.reset(); - fDone = false; - fCoincident = 0; } // OPTIMIZATION: remove this function if it's never called double t(int tIndex) const { return fTs[tIndex].fT; } + + void updatePts(const SkPoint pts[]) { + fPts = pts; + } SkPath::Verb verb() const { return fVerb; } SkScalar xAtT(double t) const { + SkASSERT(t >= 0 && t <= 1); return (*SegmentXAtT[fVerb])(fPts, t); } void xyAtT(double t, SkPoint* pt) const { + SkASSERT(t >= 0 && t <= 1); (*SegmentXYAtT[fVerb])(fPts, t, pt); } SkScalar yAtT(double t) const { + SkASSERT(t >= 0 && t <= 1); return (*SegmentYAtT[fVerb])(fPts, t); } @@ -1073,13 +1209,15 @@ public: fContainsCurves = true; } - void addLine(const SkPoint pts[2]) { + int addLine(const SkPoint pts[2]) { fSegments.push_back().addLine(pts); + return fSegments.count(); } - void addQuad(const SkPoint pts[3]) { + int addQuad(const SkPoint pts[3]) { fSegments.push_back().addQuad(pts); fContainsCurves = true; + return fSegments.count(); } const Bounds& bounds() const { @@ -1102,6 +1240,13 @@ public: } } + void fixOtherTIndex() { + int segmentCount = fSegments.count(); + for (int sIndex = 0; sIndex < segmentCount; ++sIndex) { + fSegments[sIndex].fixOtherTIndex(); + } + } + void reset() { fSegments.reset(); fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax); @@ -1217,12 +1362,6 @@ void complete() { } } -void startContour() { - if (!fCurrentContour) { - fCurrentContour = fContours.push_back_n(1); - } -} - void walk() { // FIXME:remove once we can access path pts directly SkPath::RawIter iter(fPath); // FIXME: access path directly when allowed @@ -1242,12 +1381,17 @@ void walk() { SkPath::Verb reducedVerb; uint8_t* verbPtr = fPathVerbs.begin(); const SkPoint* pointsPtr = fPathPts.begin(); + const SkPoint* finalCurveStart = NULL; + const SkPoint* finalCurveEnd = NULL; while ((verb = (SkPath::Verb) *verbPtr++) != SkPath::kDone_Verb) { switch (verb) { case SkPath::kMove_Verb: complete(); - startContour(); - pointsPtr += 1; + if (!fCurrentContour) { + fCurrentContour = fContours.push_back_n(1); + finalCurveEnd = pointsPtr++; + *fExtra.append() = -1; // start new contour + } continue; case SkPath::kLine_Verb: // skip degenerate points @@ -1257,12 +1401,14 @@ void walk() { } break; case SkPath::kQuad_Verb: + reducedVerb = QuadReduceOrder(&pointsPtr[-1], fReducePts); if (reducedVerb == 0) { break; // skip degenerate points } if (reducedVerb == 1) { - fCurrentContour->addLine(fReducePts.end() - 2); + *fExtra.append() = + fCurrentContour->addLine(fReducePts.end() - 2); break; } fCurrentContour->addQuad(&pointsPtr[-1]); @@ -1273,23 +1419,33 @@ void walk() { break; // skip degenerate points } if (reducedVerb == 1) { - fCurrentContour->addLine(fReducePts.end() - 2); + *fExtra.append() = + fCurrentContour->addLine(fReducePts.end() - 2); break; } if (reducedVerb == 2) { - fCurrentContour->addQuad(fReducePts.end() - 3); + *fExtra.append() = + fCurrentContour->addQuad(fReducePts.end() - 3); break; } fCurrentContour->addCubic(&pointsPtr[-1]); break; case SkPath::kClose_Verb: SkASSERT(fCurrentContour); + if (finalCurveStart && finalCurveEnd + && *finalCurveStart != *finalCurveEnd) { + *fReducePts.append() = *finalCurveStart; + *fReducePts.append() = *finalCurveEnd; + *fExtra.append() = + fCurrentContour->addLine(fReducePts.end() - 2); + } complete(); continue; default: SkDEBUGFAIL("bad verb"); return; } + finalCurveStart = &pointsPtr[verb - 1]; pointsPtr += verb; SkASSERT(fCurrentContour); } @@ -1297,6 +1453,24 @@ void walk() { if (fCurrentContour && !fCurrentContour->fSegments.count()) { fContours.pop_back(); } + // correct pointers in contours since fReducePts may have moved as it grew + int cIndex = 0; + fCurrentContour = &fContours[0]; + int extraCount = fExtra.count(); + SkASSERT(fExtra[0] == -1); + int eIndex = 0; + int rIndex = 0; + while (++eIndex < extraCount) { + int offset = fExtra[eIndex]; + if (offset < 0) { + fCurrentContour = &fContours[++cIndex]; + continue; + } + Segment& segment = fCurrentContour->fSegments[offset - 1]; + segment.updatePts(&fReducePts[rIndex]); + rIndex += segment.verb() + 1; + } + fExtra.reset(); // we're done with this } private: @@ -1306,6 +1480,7 @@ private: Contour* fCurrentContour; SkTArray& fContours; SkTDArray fReducePts; // segments created on the fly + SkTDArray fExtra; // -1 marks new contour, > 0 offsets into contour }; class Work { @@ -1318,8 +1493,10 @@ public: kCubic_Segment = SkPath::kCubic_Verb, }; - void addOtherT(int index, double other) { - fContour->fSegments[fIndex].addOtherT(index, other); + // FIXME: does it make sense to write otherIndex now if we're going to + // fix it up later? + void addOtherT(int index, double otherT, int otherIndex) { + fContour->fSegments[fIndex].addOtherT(index, otherT, otherIndex); } // Avoid collapsing t values that are close to the same since @@ -1436,29 +1613,34 @@ static void debugShowLineIntersection(int pts, const Work& wt, const Work& wn, const double wtTs[2], const double wnTs[2]) { #if DEBUG_ADD_INTERSECTING_TS if (!pts) { + SkDebugf("%s no intersect (%1.9g,%1.9g %1.9g,%1.9g) (%1.9g,%1.9g %1.9g,%1.9g)\n", + __FUNCTION__, wt.pts()[0].fX, wt.pts()[0].fY, + wt.pts()[1].fX, wt.pts()[1].fY, wn.pts()[0].fX, wn.pts()[0].fY, + wn.pts()[1].fX, wn.pts()[1].fY); return; } SkPoint wtOutPt, wnOutPt; LineXYAtT(wt.pts(), wtTs[0], &wtOutPt); LineXYAtT(wn.pts(), wnTs[0], &wnOutPt); - SkDebugf("%s wtTs[0]=%g (%g,%g, %g,%g) (%g,%g)\n", + SkDebugf("%s wtTs[0]=%g (%g,%g, %g,%g) (%g,%g)", __FUNCTION__, wtTs[0], wt.pts()[0].fX, wt.pts()[0].fY, wt.pts()[1].fX, wt.pts()[1].fY, wtOutPt.fX, wtOutPt.fY); if (pts == 2) { - SkDebugf("%s wtTs[1]=%g\n", __FUNCTION__, wtTs[1]); + SkDebugf(" wtTs[1]=%g", wtTs[1]); } - SkDebugf("%s wnTs[0]=%g (%g,%g, %g,%g) (%g,%g)\n", - __FUNCTION__, + SkDebugf(" wnTs[0]=%g (%g,%g, %g,%g) (%g,%g)\n", wnTs[0], wn.pts()[0].fX, wn.pts()[0].fY, wn.pts()[1].fX, wn.pts()[1].fY, wnOutPt.fX, wnOutPt.fY); if (pts == 2) { - SkDebugf("%s wnTs[1]=%g\n", __FUNCTION__, wnTs[1]); + SkDebugf(" wnTs[1]=%g", wnTs[1]); + SkDebugf("\n"); } #endif } static bool addIntersectTs(Contour* test, Contour* next, int winding) { + if (test != next) { if (test->bounds().fBottom < next->bounds().fTop) { return false; @@ -1467,10 +1649,11 @@ static bool addIntersectTs(Contour* test, Contour* next, int winding) { return true; } } - Work wt, wn; + Work wt; wt.init(test); - wn.init(next); do { + Work wn; + wn.init(next); if (test == next && !wn.startAfter(wt)) { continue; } @@ -1490,6 +1673,8 @@ static bool addIntersectTs(Contour* test, Contour* next, int winding) { case Work::kLine_Segment: { pts = HLineIntersect(wn.pts(), wt.left(), wt.right(), wt.y(), wt.xFlipped(), ts); + debugShowLineIntersection(pts, wt, wn, + ts.fT[1], ts.fT[0]); break; } case Work::kQuad_Segment: { @@ -1514,6 +1699,8 @@ static bool addIntersectTs(Contour* test, Contour* next, int winding) { case Work::kLine_Segment: { pts = VLineIntersect(wn.pts(), wt.top(), wt.bottom(), wt.x(), wt.yFlipped(), ts); + debugShowLineIntersection(pts, wt, wn, + ts.fT[1], ts.fT[0]); break; } case Work::kQuad_Segment: { @@ -1535,10 +1722,14 @@ static bool addIntersectTs(Contour* test, Contour* next, int winding) { case Work::kHorizontalLine_Segment: pts = HLineIntersect(wt.pts(), wn.left(), wn.right(), wn.y(), wn.xFlipped(), ts); + debugShowLineIntersection(pts, wt, wn, + ts.fT[1], ts.fT[0]); break; case Work::kVerticalLine_Segment: pts = VLineIntersect(wt.pts(), wn.top(), wn.bottom(), wn.x(), wn.yFlipped(), ts); + debugShowLineIntersection(pts, wt, wn, + ts.fT[1], ts.fT[0]); break; case Work::kLine_Segment: { pts = LineIntersect(wt.pts(), wn.pts(), ts); @@ -1625,8 +1816,8 @@ static bool addIntersectTs(Contour* test, Contour* next, int winding) { SkASSERT(ts.fT[1][pt] >= 0 && ts.fT[1][pt] <= 1); int testTAt = wt.addT(ts.fT[swap][pt], wn, coincident); int nextTAt = wn.addT(ts.fT[!swap][pt], wt, coincident); - wt.addOtherT(testTAt, ts.fT[!swap][pt]); - wn.addOtherT(nextTAt, ts.fT[swap][pt]); + wt.addOtherT(testTAt, ts.fT[!swap][pt], nextTAt); + wn.addOtherT(nextTAt, ts.fT[swap][pt], testTAt); coincident = -coincident; } } while (wn.advance()); @@ -1643,6 +1834,39 @@ static void coincidenceCheck(SkTDArray& contourList, int winding) { } } + +// OPTIMIZATION: not crazy about linear search here to find top active y. +// seems like we should break down and do the sort, or maybe sort each +// contours' segments? +// Once the segment array is built, there's no reason I can think of not to +// sort it in Y. hmmm +static Segment* findTopContour(SkTDArray& contourList, + int contourCount) { + int cIndex = 0; + Segment* topStart; + do { + Contour* topContour = contourList[cIndex]; + topStart = topContour->topSegment(); + } while (!topStart && ++cIndex < contourCount); + if (!topStart) { + return NULL; + } + SkScalar top = topStart->bounds().fTop; + for (int cTest = cIndex + 1; cTest < contourCount; ++cTest) { + Contour* contour = contourList[cTest]; + if (top < contour->bounds().fTop) { + continue; + } + Segment* test = contour->topSegment(); + if (top > test->bounds().fTop) { + cIndex = cTest; + topStart = test; + top = test->bounds().fTop; + } + } + return topStart; +} + // Each segment may have an inside or an outside. Segments contained within // winding may have insides on either side, and form a contour that should be // ignored. Segments that are coincident with opposing direction segments may @@ -1650,41 +1874,26 @@ static void coincidenceCheck(SkTDArray& contourList, int winding) { // 'Normal' segments will have one inside and one outside. Subsequent connections // when winding should follow the intersection direction. If more than one edge // is an option, choose first edge that continues the inside. - + // since we start with leftmost top edge, we'll traverse through a + // smaller angle counterclockwise to get to the next edge. static void bridge(SkTDArray& contourList) { int contourCount = contourList.count(); + int winding = 0; // there are no contours outside this one do { - // OPTIMIZATION: not crazy about linear search here to find top active y. - // seems like we should break down and do the sort, or maybe sort each - // contours' segments? - // Once the segment array is built, there's no reason I can think of not to - // sort it in Y. hmmm - int cIndex = 0; - Segment* topStart; - do { - Contour* topContour = contourList[cIndex]; - topStart = topContour->topSegment(); - } while (!topStart && ++cIndex < contourCount); + Segment* topStart = findTopContour(contourList, contourCount); if (!topStart) { break; } - SkScalar top = topStart->bounds().fTop; - for (int cTest = cIndex + 1; cTest < contourCount; ++cTest) { - Contour* contour = contourList[cTest]; - if (top < contour->bounds().fTop) { - continue; - } - Segment* test = contour->topSegment(); - if (top > test->bounds().fTop) { - cIndex = cTest; - topStart = test; - top = test->bounds().fTop; - } - } - int index; - const Segment* topSegment = topStart->findTop(index); // Start at the top. Above the top is outside, below is inside. - // follow edges to intersection + // follow edges to intersection by changing the tIndex by direction. + int tIndex, step; + const Segment* topSegment = topStart->findTop(tIndex, step); + const Segment* next = topSegment; + do { + int spanIndex; + next = next->findNext(tIndex, winding, step, spanIndex); + } while (next != topSegment); + // at intersection, stay on outside, but mark remaining edges as inside // or, only mark first pair as inside? // how is this going to work for contained (but not intersecting) @@ -1700,6 +1909,14 @@ static void bridge(SkTDArray& contourList) { } +static void fixOtherTIndex(SkTDArray& contourList) { + int contourCount = contourList.count(); + for (int cTest = 0; cTest < contourCount; ++cTest) { + Contour* contour = contourList[cTest]; + contour->fixOtherTIndex(); + } +} + static void makeContourList(SkTArray& contours, Contour& sentinel, SkTDArray& list) { int count = contours.count(); @@ -1740,6 +1957,7 @@ void simplifyx(const SkPath& path, bool asFill, SkPath& simple) { next = *nextPtr++; } while (next != &sentinel && addIntersectTs(current, next, winding)); } while (*currentPtr != &sentinel); + fixOtherTIndex(contourList); // eat through coincident edges coincidenceCheck(contourList, winding); // construct closed contours diff --git a/experimental/Intersection/Simplify.h b/experimental/Intersection/Simplify.h new file mode 100644 index 000000000..ae338eede --- /dev/null +++ b/experimental/Intersection/Simplify.h @@ -0,0 +1,18 @@ +/* + * 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 "CurveIntersection.h" +#include "Intersections.h" +#include "LineIntersection.h" +#include "LineParameters.h" +#include "SkPath.h" +#include "SkRect.h" +#include "SkTArray.h" +#include "SkTDArray.h" +#include "ShapeOps.h" +#include "TSearch.h" +#include // used for std::min + diff --git a/experimental/Intersection/SimplifyAddIntersectingTs_Test.cpp b/experimental/Intersection/SimplifyAddIntersectingTs_Test.cpp index f920ebac5..d3a9f2e0d 100644 --- a/experimental/Intersection/SimplifyAddIntersectingTs_Test.cpp +++ b/experimental/Intersection/SimplifyAddIntersectingTs_Test.cpp @@ -1,13 +1,10 @@ -#include "CurveIntersection.h" -#include "Intersections.h" -#include "LineIntersection.h" -#include "SkPath.h" -#include "SkRect.h" -#include "SkTArray.h" -#include "SkTDArray.h" -#include "ShapeOps.h" -#include "TSearch.h" -#include // used for std::min +/* + * 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 "Simplify.h" namespace SimplifyAddIntersectingTsTest { diff --git a/experimental/Intersection/SimplifyAngle_Test.cpp b/experimental/Intersection/SimplifyAngle_Test.cpp new file mode 100644 index 000000000..89123bd80 --- /dev/null +++ b/experimental/Intersection/SimplifyAngle_Test.cpp @@ -0,0 +1,183 @@ +/* + * 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 "Simplify.h" + +namespace SimplifyAngleTest { + +#include "Simplify.cpp" + +} // end of SimplifyAngleTest namespace + +#include "Intersection_Tests.h" + +static const SkPoint lines[][2] = { + { { 10, 10}, { 10, 20} }, + { { 10, 10}, { 20, 10} }, + { { 10, 10}, {-20, 10} }, + { { 10, 10}, { 10, -20} }, + { { 10, 10}, { 20, 20} }, + { { 10, 10}, {-20, -20} }, + { { 10, 10}, {-20, 40} }, + { { 10, 10}, { 40, -20} } +}; + +static const size_t lineCount = sizeof(lines) / sizeof(lines[0]); + +static const SkPoint quads[][3] = { + {{ 1, 1}, { 2, 2}, { 1, 3}}, // 0 + {{ 1, 1}, { 3, 3}, { 1, 5}}, // 1 + {{ 1, 1}, { 4, 4}, { 1, 7}}, // 2 + {{ 1, 1}, { 5, 5}, { 9, 9}}, // 3 + {{ 1, 1}, { 4, 4}, { 7, 1}}, // 4 + {{ 1, 1}, { 3, 3}, { 5, 1}}, // 5 + {{ 1, 1}, { 2, 2}, { 3, 1}}, // 6 +}; + +static const size_t quadCount = sizeof(quads) / sizeof(quads[0]); + +static const SkPoint cubics[][4] = { + {{ 1, 1}, { 2, 2}, { 2, 3}, { 1, 4}}, + {{ 1, 1}, { 3, 3}, { 3, 5}, { 1, 7}}, + {{ 1, 1}, { 4, 4}, { 4, 7}, { 1, 10}}, + {{ 1, 1}, { 5, 5}, { 8, 8}, { 9, 9}}, + {{ 1, 1}, { 4, 4}, { 7, 4}, { 10, 1}}, + {{ 1, 1}, { 3, 3}, { 5, 3}, { 7, 1}}, + {{ 1, 1}, { 2, 2}, { 3, 2}, { 4, 1}}, +}; + +static const size_t cubicCount = sizeof(cubics) / sizeof(cubics[0]); + +static void testLines(bool testFlat) { + // create angles in a circle + SkTDArray angles; + SkTDArray angleList; + SkTDArray arcTans; + size_t x; + for (x = 0; x < lineCount; ++x) { + SimplifyAngleTest::Angle* angle = angles.append(); + if (testFlat) { + angle->setFlat(lines[x], SkPath::kLine_Verb, 0, x, x + 1, false); + } else { + angle->set(lines[x], SkPath::kLine_Verb, 0, x, x + 1, false); + } + double arcTan = atan2(lines[x][0].fX - lines[x][1].fX, + lines[x][0].fY - lines[x][1].fY); + arcTans.push(arcTan); + } + for (x = 0; x < lineCount; ++x) { + angleList.push(&angles[x]); + } + QSort(angleList.begin(), angleList.end() - 1); + bool first = true; + bool wrap = false; + double base, last; + for (size_t x = 0; x < lineCount; ++x) { + const SimplifyAngleTest::Angle* angle = angleList[x]; + int span = angle->start(); +// SkDebugf("%s [%d] %1.9g (%1.9g,%1.9g %1.9g,%1.9g)\n", __FUNCTION__, +// span, arcTans[span], lines[span][0].fX, lines[span][0].fY, +// lines[span][1].fX, lines[span][1].fY); + if (first) { + base = last = arcTans[span]; + first = false; + continue; + } + if (last < arcTans[span]) { + last = arcTans[span]; + continue; + } + if (!wrap) { + if (base < arcTans[span]) { + SkDebugf("%s !wrap [%d] %g\n", __FUNCTION__, span, arcTans[span]); + SkASSERT(0); + } + last = arcTans[span]; + wrap = true; + continue; + } + SkDebugf("%s wrap [%d] %g\n", __FUNCTION__, span, arcTans[span]); + SkASSERT(0); + } +} + +static void testQuads(bool testFlat) { + SkTDArray angles; + SkTDArray angleList; + size_t x; + for (x = 0; x < quadCount; ++x) { + SimplifyAngleTest::Angle* angle = angles.append(); + if (testFlat) { + angle->setFlat(quads[x], SkPath::kQuad_Verb, 0, x, x + 1, false); + } else { + angle->set(quads[x], SkPath::kQuad_Verb, 0, x, x + 1, false); + } + } + for (x = 0; x < quadCount; ++x) { + angleList.push(&angles[x]); + } + QSort(angleList.begin(), angleList.end() - 1); + for (size_t x = 0; x < quadCount; ++x) { + *angleList[x] < *angleList[x + 1]; + SkASSERT(x == quadCount - 1 || *angleList[x] < *angleList[x + 1]); + const SimplifyAngleTest::Angle* angle = angleList[x]; + if (x != angle->start()) { + SkDebugf("%s [%d] [%d]\n", __FUNCTION__, x, angle->start()); + SkASSERT(0); + } + } +} + +static void testCubics(bool testFlat) { + SkTDArray angles; + SkTDArray angleList; + for (size_t x = 0; x < cubicCount; ++x) { + SimplifyAngleTest::Angle* angle = angles.append(); + if (testFlat) { + angle->setFlat(cubics[x], SkPath::kCubic_Verb, 0, x, x + 1, false); + } else { + angle->set(cubics[x], SkPath::kCubic_Verb, 0, x, x + 1, false); + } + angleList.push(angle); + } + QSort(angleList.begin(), angleList.end() - 1); + for (size_t x = 0; x < cubicCount; ++x) { + const SimplifyAngleTest::Angle* angle = angleList[x]; + if (x != angle->start()) { + SkDebugf("%s [%d] [%d]\n", __FUNCTION__, x, angle->start()); + SkASSERT(0); + } + } +} + +static void (*tests[])(bool) = { + testLines, + testQuads, + testCubics +}; + +static const size_t testCount = sizeof(tests) / sizeof(tests[0]); + +static void (*firstTest)(bool) = 0; +static bool skipAll = false; + +void SimplifyAngle_Test() { + if (skipAll) { + return; + } + size_t index = 0; + if (firstTest) { + while (index < testCount && tests[index] != firstTest) { + ++index; + } + } + bool firstTestComplete = false; + for ( ; index < testCount; ++index) { + (*tests[index])(true); + firstTestComplete = true; + } +} \ No newline at end of file diff --git a/experimental/Intersection/SimplifyFindNext_Test.cpp b/experimental/Intersection/SimplifyFindNext_Test.cpp new file mode 100644 index 000000000..6d47a93fb --- /dev/null +++ b/experimental/Intersection/SimplifyFindNext_Test.cpp @@ -0,0 +1,83 @@ +/* + * 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 "Simplify.h" + +namespace SimplifyFindNextTest { + +#include "Simplify.cpp" + +} // end of SimplifyFindNextTest namespace + +#include "Intersection_Tests.h" + +static const SimplifyFindNextTest::Segment* testCommon( + int start, int winding, int step, + SkTArray& contours, + SimplifyFindNextTest::EdgeBuilder& builder, const SkPath& path) { + SkTDArray contourList; + SimplifyFindNextTest::Contour sentinel; + sentinel.reset(); + makeContourList(contours, sentinel, contourList); + addIntersectTs(contourList[0], contourList[0], -1); + if (contours.count() > 1) { + SkASSERT(contours.count() == 2); + addIntersectTs(contourList[0], contourList[1], -1); + addIntersectTs(contourList[1], contourList[1], -1); + } + fixOtherTIndex(contourList); + SimplifyFindNextTest::Segment& segment = contours[0].fSegments[0]; + int spanIndex; + SimplifyFindNextTest::Segment* next = segment.findNext(start, winding, + step, spanIndex); + SkASSERT(spanIndex == 1); + return next; +} + +static void test(const SkPath& path) { + SkTArray contours; + SimplifyFindNextTest::EdgeBuilder builder(path, contours); + int start = 0; + int winding = 0; + int step = 1; + testCommon(start, winding, step, contours, builder, path); +} + +static void testLine1() { + SkPath path; + path.moveTo(2,0); + path.lineTo(1,1); + path.lineTo(0,0); + path.close(); + test(path); +} + +static void (*tests[])() = { + testLine1, +}; + +static const size_t testCount = sizeof(tests) / sizeof(tests[0]); + +static void (*firstTest)() = 0; +static bool skipAll = false; + +void SimplifyFindNext_Test() { + if (skipAll) { + return; + } + size_t index = 0; + if (firstTest) { + while (index < testCount && tests[index] != firstTest) { + ++index; + } + } + bool firstTestComplete = false; + for ( ; index < testCount; ++index) { + (*tests[index])(); + firstTestComplete = true; + } +} diff --git a/experimental/Intersection/SimplifyFindTop_Test.cpp b/experimental/Intersection/SimplifyFindTop_Test.cpp new file mode 100644 index 000000000..ade9299ab --- /dev/null +++ b/experimental/Intersection/SimplifyFindTop_Test.cpp @@ -0,0 +1,208 @@ +/* + * 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 "Simplify.h" + +namespace SimplifyFindTopTest { + +#include "Simplify.cpp" + +} // end of SimplifyFindTopTest namespace + +#include "Intersection_Tests.h" + +static const SimplifyFindTopTest::Segment* testCommon( + SkTArray& contours, + SimplifyFindTopTest::EdgeBuilder& builder, const SkPath& path) { + SkTDArray contourList; + SimplifyFindTopTest::Contour sentinel; + sentinel.reset(); + makeContourList(contours, sentinel, contourList); + addIntersectTs(contourList[0], contourList[0], -1); + if (contours.count() > 1) { + SkASSERT(contours.count() == 2); + addIntersectTs(contourList[0], contourList[1], -1); + addIntersectTs(contourList[1], contourList[1], -1); + } + fixOtherTIndex(contourList); + SimplifyFindTopTest::Segment* topStart = findTopContour(contourList, + contourList.count()); + int index, direction; + const SimplifyFindTopTest::Segment* topSegment = topStart->findTop(index, + direction); + SkASSERT(direction == 1); + return topSegment; +} + +static void test(const SkPath& path) { + SkTArray contours; + SimplifyFindTopTest::EdgeBuilder builder(path, contours); + testCommon(contours, builder, path); +} + +static void test(const SkPath& path, SkScalar x1, SkScalar y1, + SkScalar x2, SkScalar y2) { + SkTArray contours; + SimplifyFindTopTest::EdgeBuilder builder(path, contours); + const SimplifyFindTopTest::Segment* topSegment = + testCommon(contours, builder, path); + const SkPoint* pts = topSegment->pts(); + SkPoint top = pts[0]; + SkPoint bottom = pts[1]; + if (top.fY > bottom.fY) { + SkTSwap(top, bottom); + } + SkASSERT(top.fX == x1); + SkASSERT(top.fY == y1); + SkASSERT(bottom.fX == x2); + SkASSERT(bottom.fY == y2); +} + +static void testLine1() { + SkPath path; + path.moveTo(2,0); + path.lineTo(1,1); + path.lineTo(0,0); + path.close(); + test(path); +} + +static void addInnerCWTriangle(SkPath& path) { + path.moveTo(3,0); + path.lineTo(4,1); + path.lineTo(2,1); + path.close(); +} + +static void addInnerCCWTriangle(SkPath& path) { + path.moveTo(3,0); + path.lineTo(2,1); + path.lineTo(4,1); + path.close(); +} + +static void addOuterCWTriangle(SkPath& path) { + path.moveTo(3,0); + path.lineTo(6,2); + path.lineTo(0,2); + path.close(); +} + +static void addOuterCCWTriangle(SkPath& path) { + path.moveTo(3,0); + path.lineTo(0,2); + path.lineTo(6,2); + path.close(); +} + +static void testLine2() { + SkPath path; + addInnerCWTriangle(path); + addOuterCWTriangle(path); + test(path, 3, 0, 0, 2); +} + +static void testLine3() { + SkPath path; + addOuterCWTriangle(path); + addInnerCWTriangle(path); + test(path, 3, 0, 0, 2); +} + +static void testLine4() { + SkPath path; + addInnerCCWTriangle(path); + addOuterCWTriangle(path); + test(path, 3, 0, 0, 2); +} + +static void testLine5() { + SkPath path; + addOuterCWTriangle(path); + addInnerCCWTriangle(path); + test(path, 3, 0, 0, 2); +} + +static void testLine6() { + SkPath path; + addInnerCWTriangle(path); + addOuterCCWTriangle(path); + test(path, 3, 0, 0, 2); +} + +static void testLine7() { + SkPath path; + addOuterCCWTriangle(path); + addInnerCWTriangle(path); + test(path, 3, 0, 0, 2); +} + +static void testLine8() { + SkPath path; + addInnerCCWTriangle(path); + addOuterCCWTriangle(path); + test(path, 3, 0, 0, 2); +} + +static void testLine9() { + SkPath path; + addOuterCCWTriangle(path); + addInnerCCWTriangle(path); + test(path, 3, 0, 0, 2); +} + +static void testQuads() { + SkPath path; + path.moveTo(2,0); + path.quadTo(1,1, 0,0); + path.close(); + test(path); +} + +static void testCubics() { + SkPath path; + path.moveTo(2,0); + path.cubicTo(2,3, 1,1, 0,0); + path.close(); + test(path); +} + +static void (*tests[])() = { + testLine1, + testLine2, + testLine3, + testLine4, + testLine5, + testLine6, + testLine7, + testLine8, + testLine9, + testQuads, + testCubics +}; + +static const size_t testCount = sizeof(tests) / sizeof(tests[0]); + +static void (*firstTest)() = 0; +static bool skipAll = false; + +void SimplifyFindTop_Test() { + if (skipAll) { + return; + } + size_t index = 0; + if (firstTest) { + while (index < testCount && tests[index] != firstTest) { + ++index; + } + } + bool firstTestComplete = false; + for ( ; index < testCount; ++index) { + (*tests[index])(); + firstTestComplete = true; + } +} diff --git a/experimental/Intersection/op.htm b/experimental/Intersection/op.htm index 4df4aeb1c..5173eaba2 100644 --- a/experimental/Intersection/op.htm +++ b/experimental/Intersection/op.htm @@ -234,24 +234,37 @@ path.close();
SkPath path, out; - path.moveTo(0, 0); - path.quadTo(0, 0, 0, 0); - path.lineTo(2, 2); + path.moveTo(8, 8); + path.quadTo(10, 10, 8, -10); path.close(); - path.moveTo(0, 1); - path.lineTo(0, 1); - path.quadTo(2, 1, 3, 3); + path.moveTo(8, 8); + path.quadTo(12, 12, 14, 4); + path.close(); + path.moveTo(8, 8); + path.quadTo(9, 9, 10, 8); path.close(); testSimplify(path, true, out, bitmap); drawAsciiPaths(path, out, true); }
+
+ SkPath path, out; + path.moveTo(8.0000000000000071, 8.0000000000000071); + path.quadTo(8.7289570079366854, 8.7289570079366889, 9.3914917259458743, 9.0593802763083691); + path.close(); + path.moveTo(8.0000000000000142, 8.0000000000000142); + path.quadTo(8.1250000000000107, 8.1250000000000071, 8.2500000000000071, 8.2187500000000053); + path.close(); + testSimplify(path, true, out, bitmap); + drawAsciiPaths(path, out, true); +