shape ops work in progress
authorcaryclark@google.com <caryclark@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 18 Sep 2012 20:08:37 +0000 (20:08 +0000)
committercaryclark@google.com <caryclark@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 18 Sep 2012 20:08:37 +0000 (20:08 +0000)
at least 12M of the quad/quad intersection tests pass

git-svn-id: http://skia.googlecode.com/svn/trunk@5591 2bbb7eff-a529-9590-31e7-b0007b416f81

experimental/Intersection/DataTypes.h
experimental/Intersection/EdgeWalker_TestUtility.cpp
experimental/Intersection/LineIntersection.cpp
experimental/Intersection/LineParameters.h
experimental/Intersection/QuadraticImplicit.cpp
experimental/Intersection/QuarticRoot.cpp
experimental/Intersection/Simplify.cpp
experimental/Intersection/SimplifyNew_Test.cpp
experimental/Intersection/bc.htm
experimental/Intersection/op.htm

index 0c51a20..c87beb0 100644 (file)
@@ -74,6 +74,10 @@ inline bool approximately_zero(float x) {
     return fabs(x) < FLT_EPSILON;
 }
 
+inline bool approximately_zero_squared(double x) {
+    return fabs(x) < FLT_EPSILON * FLT_EPSILON;
+}
+
 inline bool approximately_equal(double x, double y) {
     if (approximately_zero(x - y)) {
         return true;
@@ -101,10 +105,6 @@ inline float approximately_pin(float x) {
     return approximately_zero(x) ? 0 : x;
 }
 
-inline bool approximately_zero_squared(double x) {
-    return approximately_zero(x);
-}
-
 inline bool approximately_greater_than_one(double x) {
     return x > 1 - FLT_EPSILON;
 }
index fa590c3..4897cbc 100644 (file)
@@ -136,10 +136,10 @@ static int pathsDrawTheSame(const SkPath& one, const SkPath& two,
     if (!c) {
         delete canvasPtr;
     }
-    if (errors2 >= 3 || errors > 96) {
+    if (errors2 >= 6 || errors > 160) {
         SkDebugf("%s errors2=%d errors=%d\n", __FUNCTION__, errors2, errors);
     }
-    if (errors2 >= 4 || errors > 192) {
+    if (errors2 >= 7) {
         drawAsciiPaths(scaledOne, scaledTwo, true);
     }
     error2x2 = errors2;
@@ -205,7 +205,7 @@ int comparePaths(const SkPath& one, const SkPath& two, SkBitmap& bitmap,
     if (errors2x2 == 0) {
         return 0;
     }
-    const int MAX_ERRORS = 5;
+    const int MAX_ERRORS = 8;
     if (errors2x2 > MAX_ERRORS && gComparePathsAssert) {
         SkDebugf("%s errors=%d\n", __FUNCTION__, errors);
         showPath(one);
@@ -271,6 +271,7 @@ bool testSimplifyx(SkPath& path, bool useXor, SkPath& out, State4& state,
     }
     int result = comparePaths(path, out, state.bitmap, state.canvas);
     if (result && gPathStrAssert) {
+        SkDebugf("addTest %s\n", state.filename);
         char temp[8192];
         bzero(temp, sizeof(temp));
         SkMemoryWStream stream(temp, sizeof(temp));
@@ -298,7 +299,7 @@ static int threadIndex;
 State4 threadState[maxThreadsAllocated];
 static int testNumber;
 static const char* testName;
-static bool debugThreads = false;
+static bool debugThreads = true;
 
 State4* State4::queue = NULL;
 pthread_mutex_t State4::addQueue = PTHREAD_MUTEX_INITIALIZER;
index d565d91..11f42ba 100644 (file)
@@ -28,7 +28,7 @@ int intersect(const _Line& a, const _Line& b, double aRange[2], double bRange[2]
              byLen  * axLen         -   ayLen          * bxLen == 0 ( == denom )
      */
     double denom  = byLen * axLen - ayLen * bxLen;
-    if (approximately_zero_squared(denom)) {
+    if (approximately_zero(denom)) {
        /* See if the axis intercepts match:
                   ay - ax * ayLen / axLen  ==          by - bx * ayLen / axLen
          axLen * (ay - ax * ayLen / axLen) == axLen * (by - bx * ayLen / axLen)
index d4d7419..fc1bcc8 100644 (file)
@@ -53,7 +53,7 @@ public:
 
     bool normalize() {
         double normal = sqrt(normalSquared());
-        if (approximately_zero_squared(normal)) {
+        if (approximately_zero(normal)) {
             a = b = c = 0;
             return false;
         }
index c8635d4..be54ed6 100644 (file)
@@ -91,23 +91,26 @@ bool intersect2(const Quadratic& q1, const Quadratic& q2, Intersections& i) {
     double roots1[4], roots2[4];
     int rootCount = findRoots(i2, q1, roots1);
     // OPTIMIZATION: could short circuit here if all roots are < 0 or > 1
-    int rootCount2 = findRoots(i1, q2, roots2);
+#ifndef NDEBUG
+    int rootCount2 = 
+#endif
+        findRoots(i1, q2, roots2);
     assert(rootCount == rootCount2);
     addValidRoots(roots1, rootCount, 0, i);
     addValidRoots(roots2, rootCount, 1, i);
     _Point pts[4];
     bool matches[4];
-    int index;
-    for (index = 0; index < i.fUsed2; ++index) {
-        xy_at_t(q2, i.fT[1][index], pts[index].x, pts[index].y);
-        matches[index] = false;
+    int index, ndex2;
+    for (ndex2 = 0; ndex2 < i.fUsed2; ++ndex2) {
+        xy_at_t(q2, i.fT[1][ndex2], pts[ndex2].x, pts[ndex2].y);
+        matches[ndex2] = false;
     }
     for (index = 0; index < i.fUsed; ) {
         _Point xy;
         xy_at_t(q1, i.fT[0][index], xy.x, xy.y);
-        for (int inner = 0; inner < i.fUsed2; ++inner) {
-             if (approximately_equal(pts[inner].x, xy.x) && approximately_equal(pts[inner].y, xy.y)) {
-                matches[index] = true;
+        for (ndex2 = 0; ndex2 < i.fUsed2; ++ndex2) {
+             if (approximately_equal(pts[ndex2].x, xy.x) && approximately_equal(pts[ndex2].y, xy.y)) {
+                matches[ndex2] = true;
                 goto next;
              }
         }
@@ -118,14 +121,15 @@ bool intersect2(const Quadratic& q1, const Quadratic& q2, Intersections& i) {
     next:
         ++index;
     }
-    for (index = 0; index < i.fUsed2; ) {
-        if (!matches[index]) {
-             if (--i.fUsed2 > index) {
-                memmove(&i.fT[1][index], &i.fT[1][index + 1], (i.fUsed2 - index) * sizeof(i.fT[1][0]));
+    for (ndex2 = 0; ndex2 < i.fUsed2; ) {
+        if (!matches[ndex2]) {
+             if (--i.fUsed2 > ndex2) {
+                memmove(&i.fT[1][ndex2], &i.fT[1][ndex2 + 1], (i.fUsed2 - ndex2) * sizeof(i.fT[1][0]));
+                memmove(&matches[ndex2], &matches[ndex2 + 1], (i.fUsed2 - ndex2) * sizeof(matches[0]));
                 continue;
              }
         }
-        ++index;
+        ++ndex2;
     }
     assert(i.insertBalanced());
     return i.intersected();
index e0ec2b0..8e3664b 100644 (file)
@@ -61,6 +61,8 @@ static int quadraticRootsX(const double A, const double B, const double C,
     }
 }
 
+#define USE_GEMS 0
+#if USE_GEMS
 // unlike cubicRoots in CubicUtilities.cpp, this does not discard
 // real roots <= 0 or >= 1
 static int cubicRootsX(const double A, const double B, const double C,
@@ -92,7 +94,7 @@ static int cubicRootsX(const double A, const double B, const double C,
         }
     }
     else if (R2plusQ3 < 0) { /* Casus irreducibilis: three real solutions */
-        const double theta = 1.0/3 * acos(-R / sqrt(-Q3));
+        const double theta = acos(-R / sqrt(-Q3)) / 3;
         const double _2RootQ = 2 * sqrt(-Q);
         s[0] = _2RootQ * cos(theta);
         s[1] = -_2RootQ * cos(theta + PI / 3);
@@ -106,12 +108,84 @@ static int cubicRootsX(const double A, const double B, const double C,
         num = 1;
     }
     /* resubstitute */
-    const double sub = 1.0/3 * a;
+    const double sub = a / 3;
     for (int i = 0; i < num; ++i) {
         s[i] -= sub;
     }
     return num;
 }
+#else
+
+static int cubicRootsX(double A, double B, double C, double D, double s[3]) {
+    if (approximately_zero(A)) {  // we're just a quadratic
+        return quadraticRootsX(B, C, D, s);
+    }
+    if (approximately_zero(D)) {
+        int num = quadraticRootsX(A, B, C, s);
+        for (int i = 0; i < num; ++i) {
+            if (approximately_zero(s[i])) {
+                return num;
+            }
+        }
+        s[num++] = 0;
+        return num;
+    }
+    double a, b, c;
+    {
+        double invA = 1 / A;
+        a = B * invA;
+        b = C * invA;
+        c = D * invA;
+    }
+    double a2 = a * a;
+    double Q = (a2 - b * 3) / 9;
+    double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
+    double Q3 = Q * Q * Q;
+    double R2MinusQ3 = R * R - Q3;
+    double adiv3 = a / 3;
+    double r;
+    double* roots = s;
+
+    if (R2MinusQ3 > -FLT_EPSILON / 10 && R2MinusQ3 < FLT_EPSILON / 10 ) {
+        if (approximately_zero(R)) {/* one triple solution */
+            *roots++ = -adiv3;
+        } else { /* one single and one double solution */
+            
+            double u = cube_root(-R);
+            *roots++ = 2 * u - adiv3;
+            *roots++ = -u - adiv3;
+        }
+    }
+    else if (R2MinusQ3 < 0)   // we have 3 real roots
+    {
+        double theta = acos(R / sqrt(Q3));
+        double neg2RootQ = -2 * sqrt(Q);
+
+        r = neg2RootQ * cos(theta / 3) - adiv3;
+        *roots++ = r;
+
+        r = neg2RootQ * cos((theta + 2 * PI) / 3) - adiv3;
+        *roots++ = r;
+
+        r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3;
+        *roots++ = r;
+    }
+    else                // we have 1 real root
+    {
+        double A = fabs(R) + sqrt(R2MinusQ3);
+        A = cube_root(A);
+        if (R > 0) {
+            A = -A;
+        }
+        if (A != 0) {
+            A += Q / A;
+        }
+        r = A - adiv3;
+        *roots++ = r;
+    }
+    return (int)(roots - s);
+}
+#endif
 
 int quarticRoots(const double A, const double B, const double C, const double D,
         const double E, double s[4]) {
@@ -121,8 +195,19 @@ int quarticRoots(const double A, const double B, const double C, const double D,
         }
         return cubicRootsX(B, C, D, E, s);
     }
-    double  u, v;
     int num;
+    int i;
+    if (approximately_zero(E)) {
+        num = cubicRootsX(A, B, C, D, s);
+        for (i = 0; i < num; ++i) {
+            if (approximately_zero(s[i])) {
+                return num;
+            }
+        }
+        s[num++] = 0;
+        return num;
+    }
+    double  u, v;
     /* normal form: x^4 + Ax^3 + Bx^2 + Cx + D = 0 */
     const double invA = 1 / A;
     const double a = B * invA;
@@ -165,7 +250,6 @@ int quarticRoots(const double A, const double B, const double C, const double D,
         num += quadraticRootsX(1, q < 0 ? v : -v, z + u, s + num);
     }
     // eliminate duplicates
-    int i;
     for (i = 0; i < num - 1; ++i) {
         for (int j = i + 1; j < num; ) {
             if (approximately_equal(s[i], s[j])) {
index b5403bf..827c617 100644 (file)
@@ -503,10 +503,16 @@ public:
         if (y == 0 && ry == 0 && x * rx < 0) {
             return x < rx;
         }
-        AngleValue cmp = x * ry - rx * y;
+        AngleValue x_ry = x * ry;
+        AngleValue rx_y = rx * y;
+        AngleValue cmp = x_ry - rx_y;
         if (!approximately_zero(cmp)) {
             return cmp < 0;
         }
+        if (approximately_zero(x_ry) && approximately_zero(rx_y)
+                && !approximately_zero_squared(cmp)) {
+            return cmp < 0;
+        }
         // at this point, the initial tangent line is coincident
     #if !HIGH_DEF_ANGLES // old way
         AngleValue dy = approximately_pin(fDy + fDDy);
@@ -536,6 +542,9 @@ public:
         return dx * rdy < rdx * dy;
     #else // new way
         if (fSide * rh.fSide <= 0) {
+            // FIXME: running demo will trigger this assertion
+            // (don't know if commenting out will trigger further assertion or not)
+            // commenting it out allows demo to run in release, though
             SkASSERT(fSide != rh.fSide);
             return fSide < rh.fSide;
         }
@@ -555,21 +564,35 @@ public:
     #else
         SkASSERT(fVerb == SkPath::kQuad_Verb); // worry about cubics later
         SkASSERT(rh.fVerb == SkPath::kQuad_Verb);
-        // FIXME: until I can think of something better, project a ray perpendicular from the
-        // end of the shorter tangent through both curves and use the resulting angle to sort
+        // FIXME: until I can think of something better, project a ray from the 
+        // end of the shorter tangent to midway between the end points
+        // through both curves and use the resulting angle to sort
         // FIXME: some of this setup can be moved to set() if it works, or cached if it's expensive
         double len = fTangent1.normalSquared();
         double rlen = rh.fTangent1.normalSquared();
         SkPoint ray[2];
-        const Quadratic& q = len < rlen ? fQ : rh.fQ;
-        ray[0].fX = SkDoubleToScalar(q[1].x);
-        ray[0].fY = SkDoubleToScalar(q[1].y);
-        ray[1].fX = SkDoubleToScalar((q[0].x + q[2].x) / 2);
-        ray[1].fY = SkDoubleToScalar((q[0].y + q[2].y) / 2);
         Intersections i, ri;
-        int roots = QuadRayIntersect(fPts, ray, i);
+        int roots, rroots;
+        bool flip = false;
+        do {
+            const Quadratic& q = (len < rlen) ^ flip ? fQ : rh.fQ;
+            double midX = (q[0].x + q[2].x) / 2;
+            double midY = (q[0].y + q[2].y) / 2;
+            // FIXME: Ugh! this feels like a huge hack, not sure what else to do
+            while (approximately_equal(q[1].x, midX) && approximately_equal(q[1].y, midY)) {
+                SkASSERT(midX - q[1].x || midY - q[1].y);
+                midX += midX - q[1].x;
+                midY += midY - q[1].y;
+            }
+            ray[0].fX = SkDoubleToScalar(q[1].x);
+            ray[0].fY = SkDoubleToScalar(q[1].y);
+            ray[1].fX = SkDoubleToScalar(midX);
+            ray[1].fY = SkDoubleToScalar(midY);
+            SkASSERT(ray[0] != ray[1]);
+            roots = QuadRayIntersect(fPts, ray, i);
+            rroots = QuadRayIntersect(rh.fPts, ray, ri);
+        } while ((roots == 0 || rroots == 0) && (flip ^= true));
         SkASSERT(roots > 0);
-        int rroots = QuadRayIntersect(rh.fPts, ray, ri);
         SkASSERT(rroots > 0);
         SkPoint loc;
         SkScalar best = SK_ScalarInfinity;
@@ -1499,6 +1522,8 @@ public:
         SkTDArray<Angle> angles;
         addTwoAngles(startIndex, endIndex, angles);
         buildAngles(endIndex, angles);
+        // OPTIMIZATION: check all angles to see if any have computed wind sum
+        // before sorting (early exit if none)
         SkTDArray<Angle*> sorted;
         sortAngles(angles, sorted);
 #if DEBUG_SORT
@@ -1567,13 +1592,15 @@ public:
                 continue;
             }
             SkPoint edge[4];
-            // OPTIMIZE: wrap this so that if start==0 end==fTCount-1 we can
-            // work with the original data directly
             double startT = fTs[start].fT;
             double endT = fTs[end].fT;
             (*SegmentSubDivide[fVerb])(fPts, startT, endT, edge);
             // intersect ray starting at basePt with edge
             Intersections intersections;
+            // FIXME: always use original and limit results to T values within
+            // start t and end t.
+            // OPTIMIZE: use specialty function that intersects ray with curve,
+            // returning t values only for curve (we don't care about t on ray)
             int pts = (*VSegmentIntersect[fVerb])(edge, top, bottom, basePt.fX,
                     false, intersections);
             if (pts == 0) {
@@ -1589,6 +1616,14 @@ public:
                 double testT = startT + (endT - startT) * foundT;
                 (*SegmentXYAtT[fVerb])(fPts, testT, &pt);
                 if (bestY < pt.fY && pt.fY < basePt.fY) {
+                    if (fVerb > SkPath::kLine_Verb
+                            && !approximately_less_than_zero(foundT)
+                            && !approximately_greater_than_one(foundT)) {
+                        SkScalar dx = (*SegmentDXAtT[fVerb])(fPts, testT);
+                        if (approximately_zero(dx)) {
+                            continue;
+                        }
+                    }
                     bestY = pt.fY;
                     bestT = foundT < 1 ? start : end;
                     hitT = testT;
@@ -1760,7 +1795,7 @@ public:
                 otherNonZero = bSumWinding & bXorMask;
             }
             altFlipped ^= lastNonZeroSum * sumWinding < 0; // flip if different signs
-    #if DEBUG_WINDING
+    #if 0 &&  DEBUG_WINDING
             SkASSERT(abs(sumWinding) <= gDebugMaxWindSum);
             SkDebugf("%s [%d] maxWinding=%d sumWinding=%d sign=%d altFlipped=%d\n", __FUNCTION__,
                     nextIndex, maxWinding, sumWinding, nextAngle->sign(), altFlipped);
@@ -1963,7 +1998,7 @@ public:
             nextSegment = nextAngle->segment();
             sumWinding -= nextSegment->spanSign(nextAngle);
             altFlipped ^= lastNonZeroSum * sumWinding < 0; // flip if different signs
-    #if DEBUG_WINDING
+    #if 0 && DEBUG_WINDING
             SkASSERT(abs(sumWinding) <= gDebugMaxWindSum);
             SkDebugf("%s [%d] maxWinding=%d sumWinding=%d sign=%d altFlipped=%d\n", __FUNCTION__,
                     nextIndex, maxWinding, sumWinding, nextAngle->sign(), altFlipped);
@@ -3934,6 +3969,11 @@ static int innerContourCheck(SkTDArray<Contour*>& contourList,
                 continue;
             }
             double indexDx = angle->dx();
+            test = angle->segment();
+            if (test->verb() > SkPath::kLine_Verb && approximately_zero(indexDx)) {
+                const SkPoint* pts = test->pts();
+                indexDx = pts[2].fX - pts[1].fX - indexDx;
+            }
             if (indexDx < 0) {
                 left = index;
             } else if (indexDx > 0) {
@@ -3984,6 +4024,10 @@ static int innerContourCheck(SkTDArray<Contour*>& contourList,
     }
     // see if a + change in T results in a +/- change in X (compute x'(T))
     SkScalar dx = (*SegmentDXAtT[test->verb()])(test->pts(), tHit);
+    if (test->verb() > SkPath::kLine_Verb && approximately_zero(dx)) {
+        const SkPoint* pts = test->pts();
+        dx = pts[2].fX - pts[1].fX - dx;
+    }
 #if DEBUG_WINDING
     SkDebugf("%s dx=%1.9g\n", __FUNCTION__, dx);
 #endif
index 980baa8..d317aba 100644 (file)
@@ -2698,12 +2698,152 @@ static void testQuadratic28() {
     testSimplifyx(path);
 }
 
-static void (*firstTest)() = testQuadratic28;
+static void testQuadratic29() {
+    SkPath path;
+    path.moveTo(0, 0);
+    path.quadTo(1, 0, 2, 1);
+    path.lineTo(0, 2);
+    path.close();
+    path.moveTo(0, 0);
+    path.lineTo(0, 0);
+    path.quadTo(1, 0, 0, 1);
+    path.close();
+    testSimplifyx(path);
+}
+
+static void testQuadratic30() {
+    SkPath path;
+    path.moveTo(0, 0);
+    path.quadTo(1, 0, 1, 2);
+    path.lineTo(1, 2);
+    path.close();
+    path.moveTo(0, 0);
+    path.lineTo(1, 0);
+    path.quadTo(0, 1, 1, 2);
+    path.close();
+    testSimplifyx(path);
+}
+
+static void testQuadratic31() {
+    SkPath path;
+    path.moveTo(0, 0);
+    path.quadTo(1, 0, 1, 2);
+    path.lineTo(1, 2);
+    path.close();
+    path.moveTo(0, 0);
+    path.lineTo(1, 0);
+    path.quadTo(0, 1, 1, 3);
+    path.close();
+    testSimplifyx(path);
+}
+
+static void testQuadratic32() {
+    SkPath path;
+    path.moveTo(0, 0);
+    path.quadTo(1, 0, 2, 3);
+    path.lineTo(2, 3);
+    path.close();
+    path.moveTo(0, 0);
+    path.lineTo(0, 0);
+    path.quadTo(3, 1, 0, 2);
+    path.close();
+    testSimplifyx(path);
+}
+
+static void testQuadratic33() {
+    SkPath path;
+    path.moveTo(0, 0);
+    path.quadTo(2, 0, 0, 1);
+    path.lineTo(0, 1);
+    path.close();
+    path.moveTo(0, 0);
+    path.lineTo(1, 1);
+    path.quadTo(2, 1, 2, 2);
+    path.close();
+    testSimplifyx(path);
+}
+
+static void testQuadratic34() {
+    SkPath path;
+    path.moveTo(0, 0);
+    path.quadTo(2, 0, 0, 1);
+    path.lineTo(0, 1);
+    path.close();
+    path.moveTo(1, 0);
+    path.lineTo(1, 1);
+    path.quadTo(2, 1, 1, 2);
+    path.close();
+    testSimplifyx(path);
+}
+
+static void testQuadratic35() {
+    SkPath path;
+    path.moveTo(0, 0);
+    path.quadTo(0, 1, 1, 1);
+    path.lineTo(1, 3);
+    path.close();
+    path.moveTo(2, 0);
+    path.lineTo(3, 0);
+    path.quadTo(0, 1, 1, 1);
+    path.close();
+    testSimplifyx(path);
+}
+
+static void testQuadratic36() {
+    SkPath path;
+    path.moveTo(0, 0);
+    path.quadTo(2, 1, 2, 3);
+    path.lineTo(2, 3);
+    path.close();
+    path.moveTo(3, 1);
+    path.lineTo(1, 2);
+    path.quadTo(3, 2, 1, 3);
+    path.close();
+    testSimplifyx(path);
+}
+
+static void testQuadratic37() {
+    SkPath path;
+    path.moveTo(0, 0);
+    path.quadTo(0, 2, 1, 2);
+    path.lineTo(1, 2);
+    path.close();
+    path.moveTo(0, 0);
+    path.lineTo(3, 1);
+    path.quadTo(0, 2, 1, 2);
+    path.close();
+    testSimplifyx(path);
+}
+
+static void testQuadratic38() {
+    SkPath path;
+    path.moveTo(1, 0);
+    path.quadTo(0, 1, 1, 1);
+    path.lineTo(1, 1);
+    path.close();
+    path.moveTo(1, 0);
+    path.lineTo(1, 2);
+    path.quadTo(2, 2, 1, 3);
+    path.close();
+    testSimplifyx(path);
+}
+
+static void (*firstTest)() = testQuadratic38;
 
 static struct {
     void (*fun)();
     const char* str;
 } tests[] = {
+    TEST(testQuadratic38),
+    TEST(testQuadratic37),
+    TEST(testQuadratic36),
+    TEST(testQuadratic35),
+    TEST(testQuadratic34),
+    TEST(testQuadratic33),
+    TEST(testQuadratic32),
+    TEST(testQuadratic31),
+    TEST(testQuadratic30),
+    TEST(testQuadratic29),
     TEST(testQuadratic28),
     TEST(testQuadratic27),
     TEST(testQuadratic26),
index 8a659f7..95a8d75 100644 (file)
@@ -83,11 +83,38 @@ $2 = {{
   }}
 </div>
 
+<div id="quad36">
+(gdb) p fQ
+$2 = {{
+    x = 1.8883839294261275, 
+    y = 2.1108590606904345
+  }, {
+    x = 1.888463903363252, 
+    y = 2.1111576060205435
+  }, {
+    x = 1.8885438199983176, 
+    y = 2.1114561800016824
+  }}
+(gdb) p rh.fQ
+$3 = {{
+    x = 1.8883839294260976, 
+    y = 2.1108590606903377
+  }, {
+    x = 1.8886366953645748, 
+    y = 2.1109850143489544
+  }, {
+    x = 1.8888888888888888, 
+    y = 2.1111111111111112
+  }}
+(gdb) 
+</div>
+
 </div>
 
 <script type="text/javascript">
 
 var testDivs = [
+    quad36,
     quad21g,
     quad21a,
     quad21b,
@@ -100,17 +127,23 @@ var testDivs = [
 
 var scale, columns, rows, xStart, yStart;
 
-var ticks = 0.1;
+var ticks = 10;
 var at_x = 13 + 0.5;
 var at_y = 13 + 0.5;
-var decimal_places = 0; // make this 3 to show more precision
-
+var init_decimal_places = 1; // make this 3 to show more precision
+var decimal_places;
 var tests = [];
 var testTitles = [];
 var testIndex = 0;
 var ctx;
 var fat1 = true;
 var fat2 = false;
+var ctl1 = true;
+var ctl2 = false;
+var ctlPts1 = true;
+var ctlPts2 = false;
+var minScale = 1;
+var subscale = 1;
 
 function parse(test, title) {
     var curveStrs = test.split("{{");
@@ -156,22 +189,30 @@ function init(test) {
             ymax = Math.max(ymax, curve[idx + 1]);
         }
     }
-    var subscale = 1;
+    subscale = 1;
+    decimal_places = init_decimal_places;
     if (xmax != xmin && ymax != ymin) {
         while ((xmax - xmin) * subscale < 0.1 && (ymax - ymin) * subscale < 0.1) {
             subscale *= 10;
-            if (subscale > 100000) {
-                break;
-            }
+            decimal_places += 1;
+     //       if (subscale > 100000) {
+     //           break;
+     //       }
         }
     }
-    columns = Math.ceil(xmax) - Math.floor(xmin) + 1;
-    rows = Math.ceil(ymax) - Math.floor(ymin) + 1;
-    xStart = Math.floor(xmin);
-    yStart = Math.floor(ymin);
+    columns = Math.ceil(xmax * subscale) - Math.floor(xmin * subscale) + 1;
+    rows = Math.ceil(ymax * subscale) - Math.floor(ymin * subscale) + 1;
+    
+    xStart = Math.floor(xmin * subscale) / subscale;
+    yStart = Math.floor(ymin * subscale) / subscale;
     var hscale = ctx.canvas.width / columns / ticks;
     var vscale = ctx.canvas.height / rows / ticks;
-    scale = Math.floor(Math.min(hscale, vscale)) * subscale;
+    minScale = Math.floor(Math.min(hscale, vscale));
+    scale = minScale * subscale;
+ //   while (columns < 1000 && rows < 1000) {
+  //      columns *= 2;
+ //       rows *= 2;
+ //   }
 }
 
 function drawPoint(px, py, xoffset, yoffset, unit) {
@@ -197,15 +238,15 @@ function draw(test, title, _at_x, _at_y, scale) {
     for (i = 0; i <= rows * ticks; ++i) {
         ctx.strokeStyle = (i % ticks) != 0 ? "rgb(160,160,160)" : "black";
         ctx.beginPath();
-        ctx.moveTo(_at_x + 0, _at_y + i * scale);
-        ctx.lineTo(_at_x + unit * columns, _at_y + i * scale);
+        ctx.moveTo(_at_x + 0, _at_y + i * minScale);
+        ctx.lineTo(_at_x + unit * columns, _at_y + i * minScale);
         ctx.stroke();
     }
     for (i = 0; i <= columns * ticks; ++i) {
         ctx.strokeStyle = (i % ticks) != 0 ? "rgb(160,160,160)" : "black";
         ctx.beginPath();
-        ctx.moveTo(_at_x + i * scale, _at_y + 0);
-        ctx.lineTo(_at_x + i * scale, _at_y + unit * rows);
+        ctx.moveTo(_at_x + i * minScale, _at_y + 0);
+        ctx.lineTo(_at_x + i * minScale, _at_y + unit * rows);
         ctx.stroke();
     }
  
@@ -215,13 +256,13 @@ function draw(test, title, _at_x, _at_y, scale) {
     ctx.fillStyle = "rgb(40,80,60)"
     for (i = 0; i <= columns; i += (1 / ticks))
     {
-        num = (xoffset - _at_x) / -unit + i
-        ctx.fillText(num.toFixed(0), i * unit + _at_y - 5, 10);
+        num = xStart + i / subscale
+        ctx.fillText(num.toFixed(decimal_places), xoffset + num * unit - 5, 10);
     }
     for (i = 0; i <= rows; i += (1 / ticks))
     {
-        num = (yoffset - _at_x) / -unit + i
-        ctx.fillText(num.toFixed(0), 0, i * unit + _at_y + 0);
+        num = yStart + i / subscale
+        ctx.fillText(num.toFixed(decimal_places), 0, yoffset + num * unit + 0);
     }
 
     // draw curve 1 and 2
@@ -259,6 +300,30 @@ function draw(test, title, _at_x, _at_y, scale) {
         drawFat(test[0], xoffset, yoffset, unit);
     if (fat2)
         drawFat(test[1], xoffset, yoffset, unit);
+    if (ctl1)
+        drawCtl(test[0], xoffset, yoffset, unit);
+    if (ctl2)
+        drawCtl(test[1], xoffset, yoffset, unit);
+    if (ctlPts1)
+        drawCtlPts(test[0], xoffset, yoffset, unit);
+    if (ctlPts2)
+        drawCtlPts(test[1], xoffset, yoffset, unit);
+}
+
+function drawCtl(curve, xoffset, yoffset, unit) {
+    var last = curve.length - 2;
+    ctx.strokeStyle = "rgba(0,0,0, 0.5)";
+    ctx.beginPath();
+    ctx.moveTo(xoffset + curve[0] * unit, yoffset + curve[1] * unit);
+    ctx.lineTo(xoffset + curve[2] * unit, yoffset + curve[3] * unit);
+    ctx.lineTo(xoffset + curve[4] * unit, yoffset + curve[5] * unit);
+    ctx.stroke();
+}
+
+function drawCtlPts(curve, xoffset, yoffset, unit) {
+    drawPoint(curve[0], curve[1], xoffset, yoffset, unit);
+    drawPoint(curve[2], curve[3], xoffset, yoffset, unit);
+    drawPoint(curve[4], curve[5], xoffset, yoffset, unit);
 }
 
 function drawFat(curve, xoffset, yoffset, unit) {
@@ -303,6 +368,18 @@ function redraw() {
 function doKeyPress(evt) {
     var char = String.fromCharCode(evt.charCode);
     switch (char) {
+    case 'c':
+        ctl2 ^= true;
+        if (ctl2 == false)
+            ctl1 ^= true;
+        drawTop();
+        break;
+    case 'd':
+        ctlPts2 ^= true;
+        if (ctlPts2 == false)
+            ctlPts1 ^= true;
+        drawTop();
+        break;
     case 'f':
         fat2 ^= true;
         if (fat2 == false)
index 4643ade..2063152 100644 (file)
@@ -1410,11 +1410,169 @@ path.close();
     path.close();
 </div>
 
+<div id="testQuadratic29">
+    path.moveTo(0, 0);
+    path.quadTo(1, 0, 2, 1);
+    path.lineTo(0, 2);
+    path.close();
+    path.moveTo(0, 0);
+    path.lineTo(0, 0);
+    path.quadTo(1, 0, 0, 1);
+    path.close();
+</div>
+
+<div id="testQuadratic30">
+    path.moveTo(0, 0);
+    path.quadTo(1, 0, 1, 2);
+    path.lineTo(1, 2);
+    path.close();
+    path.moveTo(0, 0);
+    path.lineTo(1, 0);
+    path.quadTo(0, 1, 1, 2);
+    path.close();
+</div>
+
+<div id="testQuadratic31">
+    path.moveTo(0, 0);
+    path.quadTo(1, 0, 1, 2);
+    path.lineTo(1, 2);
+    path.close();
+    path.moveTo(0, 0);
+    path.lineTo(1, 0);
+    path.quadTo(0, 1, 1, 3);
+    path.close();
+</div>
+
+<div id="testQuadratic32">
+    path.moveTo(0, 0);
+    path.quadTo(1, 0, 2, 3);
+    path.lineTo(2, 3);
+    path.close();
+    path.moveTo(0, 0);
+    path.lineTo(0, 0);
+    path.quadTo(3, 1, 0, 2);
+    path.close();
+</div>
+
+<div id="testQuadratic33">
+    path.moveTo(0, 0);
+    path.quadTo(2, 0, 0, 1);
+    path.lineTo(0, 1);
+    path.close();
+    path.moveTo(0, 0);
+    path.lineTo(1, 1);
+    path.quadTo(2, 1, 2, 2);
+    path.close();
+</div>
+
+<div id="testQuadratic34">
+    path.moveTo(0, 0);
+    path.quadTo(2, 0, 0, 1);
+    path.lineTo(0, 1);
+    path.close();
+    path.moveTo(1, 0);
+    path.lineTo(1, 1);
+    path.quadTo(2, 1, 1, 2);
+    path.close();
+</div>
+
+<div id="testQuadratic35">
+    path.moveTo(0, 0);
+    path.quadTo(0, 1, 1, 1);
+    path.lineTo(1, 3);
+    path.close();
+    path.moveTo(2, 0);
+    path.lineTo(3, 0);
+    path.quadTo(0, 1, 1, 1);
+    path.close();
+</div>
+
+<div id="testQuadratic36">
+    path.moveTo(0, 0);
+    path.quadTo(2, 1, 2, 3);
+    path.lineTo(2, 3);
+    path.close();
+    path.moveTo(3, 1);
+    path.lineTo(1, 2);
+    path.quadTo(3, 2, 1, 3);
+    path.close();
+</div>
+
+<div id="testQuadratic37">
+    path.moveTo(0, 0);
+    path.quadTo(0, 2, 1, 2);
+    path.lineTo(1, 2);
+    path.close();
+    path.moveTo(0, 0);
+    path.lineTo(3, 1);
+    path.quadTo(0, 2, 1, 2);
+    path.close();
+</div>
+
+<div id="testQuadratic38">
+    path.moveTo(1, 0);
+    path.quadTo(0, 1, 1, 1);
+    path.lineTo(1, 1);
+    path.close();
+    path.moveTo(1, 0);
+    path.lineTo(1, 2);
+    path.quadTo(2, 2, 1, 3);
+    path.close();
+</div>
+
+<div id="testQuadratic39">
+path.moveTo(15.5, 0);
+path.quadTo(46.5, 15.5, 46.5, 31);
+path.lineTo(15.5, 0);
+path.close();
+path.moveTo(46.5, 15.5);
+path.lineTo(0, 31);
+path.quadTo(0, 31, 15.5, 31);
+path.lineTo(46.5, 15.5);
+    path.close();
+</div>
+
+<div id="testQuadratic39a">
+path.moveTo(34.875, 19.375);
+path.lineTo(15.5, 0);
+path.quadTo(32.9687576, 8.73437881, 40.5937271, 17.4687576);
+path.lineTo(34.875, 19.375);
+path.close();
+path.moveTo(36.1666641, 20.6666679);
+path.lineTo(15.5, 31);
+path.lineTo(0, 31);
+path.lineTo(34.875, 19.375);
+path.lineTo(36.1666641, 20.6666679);
+path.close();
+path.moveTo(41.1812401, 18.15938);
+path.quadTo(46.5, 24.5796909, 46.5, 31);
+path.lineTo(36.1666641, 20.6666679);
+path.lineTo(41.1812401, 18.15938);
+path.close();
+path.moveTo(40.5937271, 17.4687576);
+path.lineTo(46.5, 15.5);
+path.lineTo(41.1812401, 18.15938);
+path.quadTo(40.8951759, 17.8140678, 40.5937271, 17.4687576);
+    path.close();
+</div>
+
 </div>
 
 <script type="text/javascript">
 
 var testDivs = [
+    testQuadratic39,
+    testQuadratic39a,
+    testQuadratic38,
+    testQuadratic37,
+    testQuadratic36,
+    testQuadratic35,
+    testQuadratic34,
+    testQuadratic33,
+    testQuadratic32,
+    testQuadratic31,
+    testQuadratic30,
+    testQuadratic29,
     testQuadratic28,
     testQuadratic27,
     testQuadratic26,