Home | History | Annotate | Download | only in tools
      1 <!DOCTYPE html>
      2 
      3 <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
      4 <head>
      5     <meta charset="utf-8" />
      6     <title></title>
      7 <div style="height:0">
      8 
      9 <div id="sect1">
     10 {{{6, -3}, {0, 1}}} id=3
     11 {{{1.6714313f, -1.08141601f}, {2.24979973f, -1.14467525f}, {2.27122664f, -0.514151096f}, {0, 1}}} id=5
     12 {{{0.001119050197303295135, 0.9992539882659912109}, {4.001119136810302734, 6.999254226684570312}}},
     13 </div>
     14 
     15 </div>
     16 
     17 <script type="text/javascript">
     18 
     19 var testDivs = [
     20 sect1,
     21 ];
     22 
     23     var decimal_places = 3;
     24 
     25     var tests = [];
     26     var testTitles = [];
     27     var testIndex = 0;
     28     var ctx;
     29 
     30     var subscale = 1;
     31     var xmin, xmax, ymin, ymax;
     32     var scale;
     33     var initScale;
     34     var mouseX, mouseY;
     35     var mouseDown = false;
     36     var srcLeft, srcTop;
     37     var screenWidth, screenHeight;
     38     var drawnPts;
     39     var curveT = 0;
     40     var curveW = -1;
     41 
     42     var lastX, lastY;
     43     var activeCurve = [];
     44     var activePt;
     45     var ids = [];
     46 
     47     var focus_on_selection = 0;
     48     var draw_t = false;
     49     var draw_w = false;
     50     var draw_closest_t = false;
     51     var draw_cubic_red = false;
     52     var draw_derivative = false;
     53     var draw_endpoints = 2;
     54     var draw_id = 0;
     55     var draw_midpoint = 0;
     56     var draw_mouse_xy = false;
     57     var draw_order = false;
     58     var draw_point_xy = false;
     59     var draw_ray_intersect = false;
     60     var draw_quarterpoint = 0;
     61     var draw_tangents = 1;
     62     var draw_sortpoint = 0;
     63     var retina_scale = !!window.devicePixelRatio;
     64 
     65     function parse(test, title) {
     66         var curveStrs = test.split("{{");
     67         var pattern = /-?\d+\.*\d*e?-?\d*/g;
     68         var curves = [];
     69         for (var c in curveStrs) {
     70             var curveStr = curveStrs[c];
     71             var idPart = curveStr.split("id=");
     72             var id = -1;
     73             if (idPart.length == 2) {
     74                 id = parseInt(idPart[1]);
     75                 curveStr = idPart[0];
     76             }
     77             var points = curveStr.match(pattern);
     78             var pts = [];
     79             for (var wd in points) {
     80                 var num = parseFloat(points[wd]);
     81                 if (isNaN(num)) continue;
     82                 pts.push(num);
     83             }
     84             if (pts.length > 2) {
     85                 curves.push(pts);
     86             }
     87             if (id >= 0) {
     88                 ids.push(id);
     89                 ids.push(pts);
     90             }
     91         }
     92         if (curves.length >= 1) {
     93             tests.push(curves);
     94             testTitles.push(title);
     95         }
     96     }
     97 
     98     function init(test) {
     99         var canvas = document.getElementById('canvas');
    100         if (!canvas.getContext) return;
    101         ctx = canvas.getContext('2d');
    102         var resScale = retina_scale && window.devicePixelRatio ? window.devicePixelRatio : 1;
    103         var unscaledWidth = window.innerWidth - 20;
    104         var unscaledHeight = window.innerHeight - 20;
    105         screenWidth = unscaledWidth;
    106         screenHeight = unscaledHeight;
    107         canvas.width = unscaledWidth * resScale;
    108         canvas.height = unscaledHeight * resScale;
    109         canvas.style.width = unscaledWidth + 'px';
    110         canvas.style.height = unscaledHeight + 'px';
    111         if (resScale != 1) {
    112             ctx.scale(resScale, resScale);
    113         }
    114         xmin = Infinity;
    115         xmax = -Infinity;
    116         ymin = Infinity;
    117         ymax = -Infinity;
    118         for (var curves in test) {
    119             var curve = test[curves];
    120             var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
    121             for (var idx = 0; idx < last; idx += 2) {
    122                 xmin = Math.min(xmin, curve[idx]);
    123                 xmax = Math.max(xmax, curve[idx]);
    124                 ymin = Math.min(ymin, curve[idx + 1]);
    125                 ymax = Math.max(ymax, curve[idx + 1]);
    126             }
    127         }
    128         xmin -= Math.min(1, Math.max(xmax - xmin, ymax - ymin));
    129         var testW = xmax - xmin;
    130         var testH = ymax - ymin;
    131         subscale = 1;
    132         while (testW * subscale < 0.1 && testH * subscale < 0.1) {
    133             subscale *= 10;
    134         }
    135         while (testW * subscale > 10 && testH * subscale > 10) {
    136             subscale /= 10;
    137         }
    138         setScale(xmin, xmax, ymin, ymax);
    139         mouseX = (screenWidth / 2) / scale + srcLeft;
    140         mouseY = (screenHeight / 2) / scale + srcTop;
    141         initScale = scale;
    142     }
    143 
    144     function setScale(x0, x1, y0, y1) {
    145         var srcWidth = x1 - x0;
    146         var srcHeight = y1 - y0;
    147         var usableWidth = screenWidth;
    148         var xDigits = Math.ceil(Math.log(Math.abs(xmax)) / Math.log(10));
    149         var yDigits = Math.ceil(Math.log(Math.abs(ymax)) / Math.log(10));
    150         usableWidth -= (xDigits + yDigits) * 10;
    151         usableWidth -= decimal_places * 10;
    152         var hscale = usableWidth / srcWidth;
    153         var vscale = screenHeight / srcHeight;
    154         scale = Math.min(hscale, vscale);
    155         var invScale = 1 / scale;
    156         var sxmin = x0 - invScale * 5;
    157         var symin = y0 - invScale * 10;
    158         var sxmax = x1 + invScale * (6 * decimal_places + 10);
    159         var symax = y1 + invScale * 10;
    160         srcWidth = sxmax - sxmin;
    161         srcHeight = symax - symin;
    162         hscale = usableWidth / srcWidth;
    163         vscale = screenHeight / srcHeight;
    164         scale = Math.min(hscale, vscale);
    165         srcLeft = sxmin;
    166         srcTop = symin;
    167     }
    168 
    169 function dxy_at_t(curve, t) {
    170     var dxy = {};
    171     if (curve.length == 6) {
    172         var a = t - 1;
    173         var b = 1 - 2 * t;
    174         var c = t;
    175         dxy.x = a * curve[0] + b * curve[2] + c * curve[4];
    176         dxy.y = a * curve[1] + b * curve[3] + c * curve[5];
    177     } else if (curve.length == 7) {
    178         var p20x = curve[4] - curve[0];
    179         var p20y = curve[5] - curve[1];
    180         var p10xw = (curve[2] - curve[0]) * curve[6];
    181         var p10yw = (curve[3] - curve[1]) * curve[6];
    182         var coeff0x = curve[6] * p20x - p20x;
    183         var coeff0y = curve[6] * p20y - p20y;
    184         var coeff1x = p20x - 2 * p10xw;
    185         var coeff1y = p20y - 2 * p10yw;
    186         dxy.x = t * (t * coeff0x + coeff1x) + p10xw;
    187         dxy.y = t * (t * coeff0y + coeff1y) + p10yw;
    188     } else if (curve.length == 8) {
    189         var one_t = 1 - t;
    190         var a = curve[0];
    191         var b = curve[2];
    192         var c = curve[4];
    193         var d = curve[6];
    194         dxy.x = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
    195         a = curve[1];
    196         b = curve[3];
    197         c = curve[5];
    198         d = curve[7];
    199         dxy.y = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
    200     }
    201     return dxy;
    202 }
    203 
    204     var flt_epsilon = 1.19209290E-07;
    205 
    206     function approximately_zero(A) {
    207         return Math.abs(A) < flt_epsilon;
    208     }
    209 
    210     function approximately_zero_inverse(A) {
    211         return Math.abs(A) > (1 / flt_epsilon);
    212     }
    213 
    214     function quad_real_roots(A, B, C) {
    215         var s = [];
    216         var p = B / (2 * A);
    217         var q = C / A;
    218         if (approximately_zero(A) && (approximately_zero_inverse(p)
    219                 || approximately_zero_inverse(q))) {
    220             if (approximately_zero(B)) {
    221                 if (C == 0) {
    222                     s[0] = 0;
    223                 }
    224                 return s;
    225             }
    226             s[0] = -C / B;
    227             return s;
    228         }
    229         /* normal form: x^2 + px + q = 0 */
    230         var p2 = p * p;
    231         if (!approximately_zero(p2 - q) && p2 < q) {
    232             return s;
    233         }
    234         var sqrt_D = 0;
    235         if (p2 > q) {
    236             sqrt_D = Math.sqrt(p2 - q);
    237         }
    238         s[0] = sqrt_D - p;
    239         var flip = -sqrt_D - p;
    240         if (!approximately_zero(s[0] - flip)) {
    241             s[1] = flip;
    242         }
    243         return s;
    244     }
    245 
    246     function cubic_real_roots(A, B, C, D) {
    247         if (approximately_zero(A)) {  // we're just a quadratic
    248             return quad_real_roots(B, C, D);
    249         }
    250         if (approximately_zero(D)) {  // 0 is one root
    251             var s = quad_real_roots(A, B, C);
    252             for (var i = 0; i < s.length; ++i) {
    253                 if (approximately_zero(s[i])) {
    254                     return s;
    255                 }
    256             }
    257             s.push(0);
    258             return s;
    259         }
    260         if (approximately_zero(A + B + C + D)) {  // 1 is one root
    261             var s = quad_real_roots(A, A + B, -D);
    262             for (var i = 0; i < s.length; ++i) {
    263                 if (approximately_zero(s[i] - 1)) {
    264                     return s;
    265                 }
    266             }
    267             s.push(1);
    268             return s;
    269         }
    270         var a, b, c;
    271         var invA = 1 / A;
    272         a = B * invA;
    273         b = C * invA;
    274         c = D * invA;
    275         var a2 = a * a;
    276         var Q = (a2 - b * 3) / 9;
    277         var R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
    278         var R2 = R * R;
    279         var Q3 = Q * Q * Q;
    280         var R2MinusQ3 = R2 - Q3;
    281         var adiv3 = a / 3;
    282         var r;
    283         var roots = [];
    284         if (R2MinusQ3 < 0) {   // we have 3 real roots
    285             var theta = Math.acos(R / Math.sqrt(Q3));
    286             var neg2RootQ = -2 * Math.sqrt(Q);
    287             r = neg2RootQ * Math.cos(theta / 3) - adiv3;
    288             roots.push(r);
    289             r = neg2RootQ * Math.cos((theta + 2 * Math.PI) / 3) - adiv3;
    290             if (!approximately_zero(roots[0] - r)) {
    291                 roots.push(r);
    292             }
    293             r = neg2RootQ * Math.cos((theta - 2 * Math.PI) / 3) - adiv3;
    294             if (!approximately_zero(roots[0] - r) && (roots.length == 1
    295                         || !approximately_zero(roots[1] - r))) {
    296                 roots.push(r);
    297             }
    298         } else {  // we have 1 real root
    299             var sqrtR2MinusQ3 = Math.sqrt(R2MinusQ3);
    300             var A = Math.abs(R) + sqrtR2MinusQ3;
    301             A = Math.pow(A, 1/3);
    302             if (R > 0) {
    303                 A = -A;
    304             }
    305             if (A != 0) {
    306                 A += Q / A;
    307             }
    308             r = A - adiv3;
    309             roots.push(r);
    310             if (approximately_zero(R2 - Q3)) {
    311                 r = -A / 2 - adiv3;
    312                 if (!approximately_zero(roots[0] - r)) {
    313                     roots.push(r);
    314                 }
    315             }
    316         }
    317         return roots;
    318     }
    319 
    320     function approximately_zero_or_more(tValue) {
    321         return tValue >= -flt_epsilon;
    322     }
    323 
    324     function approximately_one_or_less(tValue) {
    325         return tValue <= 1 + flt_epsilon;
    326     }
    327 
    328     function approximately_less_than_zero(tValue) {
    329         return tValue < flt_epsilon;
    330     }
    331 
    332     function approximately_greater_than_one(tValue) {
    333         return tValue > 1 - flt_epsilon;
    334     }
    335 
    336     function add_valid_ts(s) {
    337         var t = [];
    338     nextRoot:
    339         for (var index = 0; index < s.length; ++index) {
    340             var tValue = s[index];
    341             if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) {
    342                 if (approximately_less_than_zero(tValue)) {
    343                     tValue = 0;
    344                 } else if (approximately_greater_than_one(tValue)) {
    345                     tValue = 1;
    346                 }
    347                 for (var idx2 = 0; idx2 < t.length; ++idx2) {
    348                     if (approximately_zero(t[idx2] - tValue)) {
    349                         continue nextRoot;
    350                     }
    351                 }
    352                 t.push(tValue);
    353             }
    354         }
    355         return t;
    356     }
    357 
    358     function quad_roots(A, B, C) {
    359         var s = quad_real_roots(A, B, C);
    360         var foundRoots = add_valid_ts(s);
    361         return foundRoots;
    362     }
    363 
    364     function cubic_roots(A, B, C, D) {
    365         var s = cubic_real_roots(A, B, C, D);
    366         var foundRoots = add_valid_ts(s);
    367         return foundRoots;
    368     }
    369 
    370     function ray_curve_intersect(startPt, endPt, curve) {
    371         var adj = endPt[0] - startPt[0];
    372         var opp = endPt[1] - startPt[1];
    373         var r = [];
    374         var len = (curve.length == 7 ? 6 : curve.length) / 2;
    375         for (var n = 0; n < len; ++n) {
    376             r[n] = (curve[n * 2 + 1] - startPt[1]) * adj - (curve[n * 2] - startPt[0]) * opp;
    377         }
    378         if (curve.length == 6) {
    379             var A = r[2];
    380             var B = r[1];
    381             var C = r[0];
    382             A += C - 2 * B;  // A = a - 2*b + c
    383             B -= C;  // B = -(b - c)
    384             return quad_roots(A, 2 * B, C);
    385         }
    386         if (curve.length == 7) {
    387             var A = r[2];
    388             var B = r[1] * curve[6];
    389             var C = r[0];
    390             A += C - 2 * B;  // A = a - 2*b + c
    391             B -= C;  // B = -(b - c)
    392             return quad_roots(A, 2 * B, C);
    393         }
    394         var A = r[3];       // d
    395         var B = r[2] * 3;   // 3*c
    396         var C = r[1] * 3;   // 3*b
    397         var D = r[0];       // a
    398         A -= D - C + B;     // A =   -a + 3*b - 3*c + d
    399         B += 3 * D - 2 * C; // B =  3*a - 6*b + 3*c
    400         C -= 3 * D;         // C = -3*a + 3*b
    401         return cubic_roots(A, B, C, D);
    402     }
    403 
    404     function x_at_t(curve, t) {
    405         var one_t = 1 - t;
    406         if (curve.length == 4) {
    407             return one_t * curve[0] + t * curve[2];
    408         }
    409         var one_t2 = one_t * one_t;
    410         var t2 = t * t;
    411         if (curve.length == 6) {
    412             return one_t2 * curve[0] + 2 * one_t * t * curve[2] + t2 * curve[4];
    413         }
    414         if (curve.length == 7) {
    415             var numer = one_t2 * curve[0] + 2 * one_t * t * curve[2] * curve[6]
    416                     + t2 * curve[4];
    417             var denom = one_t2            + 2 * one_t * t            * curve[6]
    418                     + t2;
    419             return numer / denom;
    420         }
    421         var a = one_t2 * one_t;
    422         var b = 3 * one_t2 * t;
    423         var c = 3 * one_t * t2;
    424         var d = t2 * t;
    425         return a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6];
    426     }
    427 
    428     function y_at_t(curve, t) {
    429         var one_t = 1 - t;
    430         if (curve.length == 4) {
    431             return one_t * curve[1] + t * curve[3];
    432         }
    433         var one_t2 = one_t * one_t;
    434         var t2 = t * t;
    435         if (curve.length == 6) {
    436             return one_t2 * curve[1] + 2 * one_t * t * curve[3] + t2 * curve[5];
    437         }
    438         if (curve.length == 7) {
    439             var numer = one_t2 * curve[1] + 2 * one_t * t * curve[3] * curve[6]
    440                     + t2 * curve[5];
    441             var denom = one_t2            + 2 * one_t * t            * curve[6]
    442                     + t2;
    443             return numer / denom;
    444         }
    445         var a = one_t2 * one_t;
    446         var b = 3 * one_t2 * t;
    447         var c = 3 * one_t * t2;
    448         var d = t2 * t;
    449         return a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7];
    450     }
    451 
    452     function drawPointAtT(curve) {
    453         var x = x_at_t(curve, curveT);
    454         var y = y_at_t(curve, curveT);
    455         drawPoint(x, y);
    456     }
    457 
    458     function drawLine(x1, y1, x2, y2) {
    459         ctx.beginPath();
    460         ctx.moveTo((x1 - srcLeft) * scale,
    461                 (y1 - srcTop) * scale);
    462         ctx.lineTo((x2 - srcLeft) * scale,
    463                 (y2 - srcTop) * scale);
    464         ctx.stroke();
    465     }
    466 
    467     function drawPoint(px, py) {
    468         for (var pts = 0; pts < drawnPts.length; pts += 2) {
    469             var x = drawnPts[pts];
    470             var y = drawnPts[pts + 1];
    471             if (px == x && py == y) {
    472                 return;
    473             }
    474         }
    475         drawnPts.push(px);
    476         drawnPts.push(py);
    477         var _px = (px - srcLeft) * scale;
    478         var _py = (py - srcTop) * scale;
    479         ctx.beginPath();
    480         ctx.arc(_px, _py, 3, 0, Math.PI * 2, true);
    481         ctx.closePath();
    482         ctx.stroke();
    483         if (draw_point_xy) {
    484             var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places);
    485             ctx.font = "normal 10px Arial";
    486             ctx.textAlign = "left";
    487             ctx.fillStyle = "black";
    488             ctx.fillText(label, _px + 5, _py);
    489         }
    490     }
    491 
    492     function drawPointSolid(px, py) {
    493         drawPoint(px, py);
    494         ctx.fillStyle = "rgba(0,0,0, 0.4)";
    495         ctx.fill();
    496     }
    497 
    498     function crossPt(origin, pt1, pt2) {
    499         return ((pt1[0] - origin[0]) * (pt2[1] - origin[1])
    500               - (pt1[1] - origin[1]) * (pt2[0] - origin[0])) > 0 ? 0 : 1;
    501     }
    502 
    503     // may not work well for cubics
    504     function curveClosestT(curve, x, y) {
    505         var closest = -1;
    506         var closestDist = Infinity;
    507         var l = Infinity, t = Infinity, r = -Infinity, b = -Infinity;
    508         for (var i = 0; i < 16; ++i) {
    509             var testX = x_at_t(curve, i / 16);
    510             l = Math.min(testX, l);
    511             r = Math.max(testX, r);
    512             var testY = y_at_t(curve, i / 16);
    513             t = Math.min(testY, t);
    514             b = Math.max(testY, b);
    515             var dx = testX - x;
    516             var dy = testY - y;
    517             var dist = dx * dx + dy * dy;
    518             if (closestDist > dist) {
    519                 closestDist = dist;
    520                 closest = i;
    521             }
    522         }
    523         var boundsX = r - l;
    524         var boundsY = b - t;
    525         var boundsDist = boundsX * boundsX + boundsY * boundsY;
    526         if (closestDist > boundsDist) {
    527             return -1;
    528         }
    529         console.log("closestDist = " + closestDist + " boundsDist = " + boundsDist
    530                 + " t = " + closest / 16);
    531         return closest / 16;
    532     }
    533 
    534     var kMaxConicToQuadPOW2 = 5;
    535 
    536     function computeQuadPOW2(curve, tol) {
    537         var a = curve[6] - 1;
    538         var k = a / (4 * (2 + a));
    539         var x = k * (curve[0] - 2 * curve[2] + curve[4]);
    540         var y = k * (curve[1] - 2 * curve[3] + curve[5]);
    541 
    542         var error = Math.sqrt(x * x + y * y);
    543         var pow2;
    544         for (pow2 = 0; pow2 < kMaxConicToQuadPOW2; ++pow2) {
    545             if (error <= tol) {
    546                 break;
    547             }
    548             error *= 0.25;
    549         }
    550         return pow2;
    551     }
    552 
    553     function subdivide_w_value(w) {
    554         return Math.sqrt(0.5 + w * 0.5);
    555     }
    556 
    557     function chop(curve, part1, part2) {
    558         var w = curve[6];
    559         var scale = 1 / (1 + w);
    560         part1[0] = curve[0];
    561         part1[1] = curve[1];
    562         part1[2] = (curve[0] + curve[2] * w) * scale;
    563         part1[3] = (curve[1] + curve[3] * w) * scale;
    564         part1[4] = part2[0] = (curve[0] + (curve[2] * w) * 2 + curve[4]) * scale * 0.5;
    565         part1[5] = part2[1] = (curve[1] + (curve[3] * w) * 2 + curve[5]) * scale * 0.5;
    566         part2[2] = (curve[2] * w + curve[4]) * scale;
    567         part2[3] = (curve[3] * w + curve[5]) * scale;
    568         part2[4] = curve[4];
    569         part2[5] = curve[5];
    570         part1[6] = part2[6] = subdivide_w_value(w);
    571     }
    572 
    573     function subdivide(curve, level, pts) {
    574         if (0 == level) {
    575             pts.push(curve[2]);
    576             pts.push(curve[3]);
    577             pts.push(curve[4]);
    578             pts.push(curve[5]);
    579         } else {
    580             var part1 = [], part2 = [];
    581             chop(curve, part1, part2);
    582             --level;
    583             subdivide(part1, level, pts);
    584             subdivide(part2, level, pts);
    585         }
    586     }
    587 
    588     function chopIntoQuadsPOW2(curve, pow2, pts) {
    589         subdivide(curve, pow2, pts);
    590         return 1 << pow2;
    591     }
    592 
    593     function drawConic(curve, srcLeft, srcTop, scale) {
    594         var tol = 1 / scale;
    595         var pow2 = computeQuadPOW2(curve, tol);
    596         var pts = [];
    597         chopIntoQuadsPOW2(curve, pow2, pts);
    598         for (var i = 0; i < pts.length; i += 4) {
    599             ctx.quadraticCurveTo(
    600                 (pts[i + 0] - srcLeft) * scale, (pts[i + 1] - srcTop) * scale,
    601                 (pts[i + 2] - srcLeft) * scale, (pts[i + 3] - srcTop) * scale);
    602         }
    603     }
    604 
    605     function draw(test, title) {
    606         ctx.font = "normal 50px Arial";
    607         ctx.textAlign = "left";
    608         ctx.fillStyle = "rgba(0,0,0, 0.1)";
    609         ctx.fillText(title, 50, 50);
    610         ctx.font = "normal 10px Arial";
    611         //  ctx.lineWidth = "1.001"; "0.999";
    612         var hullStarts = [];
    613         var hullEnds = [];
    614         var midSpokes = [];
    615         var midDist = [];
    616         var origin = [];
    617         var shortSpokes = [];
    618         var shortDist = [];
    619         var sweeps = [];
    620         drawnPts = [];
    621         for (var curves in test) {
    622             var curve = test[curves];
    623             origin.push(curve[0]);
    624             origin.push(curve[1]);
    625             var startPt = [];
    626             startPt.push(curve[2]);
    627             startPt.push(curve[3]);
    628             hullStarts.push(startPt);
    629             var endPt = [];
    630             if (curve.length == 4) {
    631                 endPt.push(curve[2]);
    632                 endPt.push(curve[3]);
    633             } else if (curve.length == 6 || curve.length == 7) {
    634                 endPt.push(curve[4]);
    635                 endPt.push(curve[5]);
    636             } else if (curve.length == 8) {
    637                 endPt.push(curve[6]);
    638                 endPt.push(curve[7]);
    639             }
    640             hullEnds.push(endPt);
    641             var sweep = crossPt(origin, startPt, endPt);
    642             sweeps.push(sweep);
    643             var midPt = [];
    644             midPt.push(x_at_t(curve, 0.5));
    645             midPt.push(y_at_t(curve, 0.5));
    646             midSpokes.push(midPt);
    647             var shortPt = [];
    648             shortPt.push(x_at_t(curve, 0.25));
    649             shortPt.push(y_at_t(curve, 0.25));
    650             shortSpokes.push(shortPt);
    651             var dx = midPt[0] - origin[0];
    652             var dy = midPt[1] - origin[1];
    653             var dist = Math.sqrt(dx * dx + dy * dy);
    654             midDist.push(dist);
    655             dx = shortPt[0] - origin[0];
    656             dy = shortPt[1] - origin[1];
    657             dist = Math.sqrt(dx * dx + dy * dy);
    658             shortDist.push(dist);
    659         }
    660         var intersect = [];
    661         var useIntersect = false;
    662         var maxWidth = Math.max(xmax - xmin, ymax - ymin);
    663         for (var curves in test) {
    664             var curve = test[curves];
    665             if (curve.length >= 6 && curve.length <= 8) {
    666                 var opp = curves == 0 || curves == 1 ? 0 : 1;
    667                 var sects = ray_curve_intersect(origin, hullEnds[opp], curve);
    668                 intersect.push(sects);
    669                 if (sects.length > 1) {
    670                     var intersection = sects[0];
    671                     if (intersection == 0) {
    672                         intersection = sects[1];
    673                     }
    674                     var ix = x_at_t(curve, intersection) - origin[0];
    675                     var iy = y_at_t(curve, intersection) - origin[1];
    676                     var ex = hullEnds[opp][0] - origin[0];
    677                     var ey = hullEnds[opp][1] - origin[1];
    678                     if (ix * ex >= 0 && iy * ey >= 0) {
    679                         var iDist = Math.sqrt(ix * ix + iy * iy);
    680                         var eDist = Math.sqrt(ex * ex + ey * ey);
    681                         var delta = Math.abs(iDist - eDist) / maxWidth;
    682                         if (delta > (curve.length != 8 ? 1e-5 : 1e-4)) {
    683                             useIntersect ^= true;
    684                         }
    685                     }
    686                 }
    687             }
    688         }
    689         var midLeft = curves != 0 ? crossPt(origin, midSpokes[0], midSpokes[1]) : 0;
    690         var firstInside;
    691         if (useIntersect) {
    692             var sect1 = intersect[0].length > 1;
    693             var sIndex = sect1 ? 0 : 1;
    694             var sects = intersect[sIndex];
    695             var intersection = sects[0];
    696             if (intersection == 0) {
    697                 intersection = sects[1];
    698             }
    699             var curve = test[sIndex];
    700             var ix = x_at_t(curve, intersection) - origin[0];
    701             var iy = y_at_t(curve, intersection) - origin[1];
    702             var opp = sect1 ? 1 : 0;
    703             var ex = hullEnds[opp][0] - origin[0];
    704             var ey = hullEnds[opp][1] - origin[1];
    705             var iDist = ix * ix + iy * iy;
    706             var eDist = ex * ex + ey * ey;
    707             firstInside = (iDist > eDist) ^ (sIndex == 0) ^ sweeps[0];
    708 //            console.log("iDist=" + iDist + " eDist=" + eDist + " sIndex=" + sIndex
    709  //                   + " sweeps[0]=" + sweeps[0]);
    710         } else {
    711  //           console.log("midLeft=" + midLeft);
    712             firstInside = midLeft != 0;
    713         }
    714         var shorter = midDist[1] < midDist[0];
    715         var shortLeft = shorter ? crossPt(origin, shortSpokes[0], midSpokes[1])
    716                 : crossPt(origin, midSpokes[0], shortSpokes[1]);
    717         var startCross = crossPt(origin, hullStarts[0], hullStarts[1]);
    718         var disallowShort = midLeft == startCross && midLeft == sweeps[0]
    719                     && midLeft == sweeps[1];
    720 
    721   //      console.log("midLeft=" + midLeft + " startCross=" + startCross);
    722         var intersectIndex = 0;
    723         for (var curves in test) {
    724             var curve = test[draw_id != 2 ? curves : test.length - curves - 1];
    725             if (curve.length != 4 && curve.length != 6 && curve.length != 7 && curve.length != 8) {
    726                 continue;
    727             }
    728             ctx.lineWidth = 1;
    729             if (draw_tangents != 0) {
    730                 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
    731                     ctx.strokeStyle = "rgba(255,0,0, 0.3)";
    732                 } else {
    733                     ctx.strokeStyle = "rgba(0,0,255, 0.3)";
    734                 }
    735                 drawLine(curve[0], curve[1], curve[2], curve[3]);
    736                 if (draw_tangents != 2) {
    737                     if (curve.length > 4) drawLine(curve[2], curve[3], curve[4], curve[5]);
    738                     if (curve.length == 8) drawLine(curve[4], curve[5], curve[6], curve[7]);
    739                 }
    740                 if (draw_tangents != 1) {
    741                     if (curve.length == 6 || curve.length == 7) {
    742                         drawLine(curve[0], curve[1], curve[4], curve[5]);
    743                     }
    744                     if (curve.length == 8) drawLine(curve[0], curve[1], curve[6], curve[7]);
    745                 }
    746             }
    747             ctx.beginPath();
    748             ctx.moveTo((curve[0] - srcLeft) * scale, (curve[1] - srcTop) * scale);
    749             if (curve.length == 4) {
    750                 ctx.lineTo((curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale);
    751             } else if (curve.length == 6) {
    752                 ctx.quadraticCurveTo(
    753                     (curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale,
    754                     (curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale);
    755             } else if (curve.length == 7) {
    756                 drawConic(curve, srcLeft, srcTop, scale);
    757             } else {
    758                 ctx.bezierCurveTo(
    759                     (curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale,
    760                     (curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale,
    761                     (curve[6] - srcLeft) * scale, (curve[7] - srcTop) * scale);
    762             }
    763             if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
    764                 ctx.strokeStyle = "rgba(255,0,0, 1)";
    765             } else {
    766                 ctx.strokeStyle = "rgba(0,0,255, 1)";
    767             }
    768             ctx.stroke();
    769             if (draw_endpoints > 0) {
    770                 drawPoint(curve[0], curve[1]);
    771                 if (draw_endpoints > 1 || curve.length == 4) {
    772                     drawPoint(curve[2], curve[3]);
    773                 }
    774                 if (curve.length == 6 || curve.length == 7 ||
    775                         (draw_endpoints > 1 && curve.length == 8)) {
    776                     drawPoint(curve[4], curve[5]);
    777                 }
    778                 if (curve.length == 8) drawPoint(curve[6], curve[7]);
    779             }
    780             if (draw_midpoint != 0) {
    781                 if ((curves == 0) == (midLeft == 0)) {
    782                     ctx.strokeStyle = "rgba(0,180,127, 0.6)";
    783                 } else {
    784                     ctx.strokeStyle = "rgba(127,0,127, 0.6)";
    785                 }
    786                 var midX = x_at_t(curve, 0.5);
    787                 var midY = y_at_t(curve, 0.5);
    788                 drawPointSolid(midX, midY);
    789                 if (draw_midpoint > 1) {
    790                     drawLine(curve[0], curve[1], midX, midY);
    791                 }
    792             }
    793             if (draw_quarterpoint != 0) {
    794                 if ((curves == 0) == (shortLeft == 0)) {
    795                     ctx.strokeStyle = "rgba(0,191,63, 0.6)";
    796                 } else {
    797                     ctx.strokeStyle = "rgba(63,0,191, 0.6)";
    798                 }
    799                 var midT = (curves == 0) == shorter ? 0.25 : 0.5;
    800                 var midX = x_at_t(curve, midT);
    801                 var midY = y_at_t(curve, midT);
    802                 drawPointSolid(midX, midY);
    803                 if (draw_quarterpoint > 1) {
    804                     drawLine(curve[0], curve[1], midX, midY);
    805                 }
    806             }
    807             if (draw_sortpoint != 0) {
    808                 if ((curves == 0) == ((disallowShort == -1 ? midLeft : shortLeft) == 0)) {
    809                     ctx.strokeStyle = "rgba(0,155,37, 0.6)";
    810                 } else {
    811                     ctx.strokeStyle = "rgba(37,0,155, 0.6)";
    812                 }
    813                 var midT = (curves == 0) == shorter && disallowShort != curves ? 0.25 : 0.5;
    814                 console.log("curves=" + curves + " disallowShort=" + disallowShort
    815                         + " midLeft=" + midLeft + " shortLeft=" + shortLeft
    816                         + " shorter=" + shorter + " midT=" + midT);
    817                 var midX = x_at_t(curve, midT);
    818                 var midY = y_at_t(curve, midT);
    819                 drawPointSolid(midX, midY);
    820                 if (draw_sortpoint > 1) {
    821                     drawLine(curve[0], curve[1], midX, midY);
    822                 }
    823             }
    824             if (draw_ray_intersect != 0) {
    825                 ctx.strokeStyle = "rgba(75,45,199, 0.6)";
    826                 if (curve.length >= 6 && curve.length <= 8) {
    827                     var intersections = intersect[intersectIndex];
    828                     for (var i in intersections) {
    829                         var intersection = intersections[i];
    830                         var x = x_at_t(curve, intersection);
    831                         var y = y_at_t(curve, intersection);
    832                         drawPointSolid(x, y);
    833                         if (draw_ray_intersect > 1) {
    834                             drawLine(curve[0], curve[1], x, y);
    835                         }
    836                     }
    837                 }
    838                 ++intersectIndex;
    839             }
    840             if (draw_order) {
    841                 var px = x_at_t(curve, 0.75);
    842                 var py = y_at_t(curve, 0.75);
    843                 var _px = (px - srcLeft) * scale;
    844                 var _py = (py - srcTop) * scale;
    845                 ctx.beginPath();
    846                 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
    847                 ctx.closePath();
    848                 ctx.fillStyle = "white";
    849                 ctx.fill();
    850                 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
    851                     ctx.strokeStyle = "rgba(255,0,0, 1)";
    852                     ctx.fillStyle = "rgba(255,0,0, 1)";
    853                 } else {
    854                     ctx.strokeStyle = "rgba(0,0,255, 1)";
    855                     ctx.fillStyle = "rgba(0,0,255, 1)";
    856                 }
    857                 ctx.stroke();
    858                 ctx.font = "normal 16px Arial";
    859                 ctx.textAlign = "center";
    860                 ctx.fillText(parseInt(curves) + 1, _px, _py + 5);
    861             }
    862             if (draw_closest_t) {
    863                 var t = curveClosestT(curve, mouseX, mouseY);
    864                 if (t >= 0) {
    865                     var x = x_at_t(curve, t);
    866                     var y = y_at_t(curve, t);
    867                     drawPointSolid(x, y);
    868                 }
    869             }
    870             if (!approximately_zero(scale - initScale)) {
    871                 ctx.font = "normal 20px Arial";
    872                 ctx.fillStyle = "rgba(0,0,0, 0.3)";
    873                 ctx.textAlign = "right";
    874                 ctx.fillText(scale.toFixed(decimal_places) + 'x',
    875                         screenWidth - 10, screenHeight - 5);
    876             }
    877             if (draw_t) {
    878                 drawPointAtT(curve);
    879             }
    880             if (draw_id != 0) {
    881                 var id = -1;
    882                 for (var i = 0; i < ids.length; i += 2) {
    883                     if (ids[i + 1] == curve) {
    884                         id = ids[i];
    885                         break;
    886                     }
    887                 }
    888                 if (id >= 0) {
    889                     var px = x_at_t(curve, 0.5);
    890                     var py = y_at_t(curve, 0.5);
    891                     var _px = (px - srcLeft) * scale;
    892                     var _py = (py - srcTop) * scale;
    893                     ctx.beginPath();
    894                     ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
    895                     ctx.closePath();
    896                     ctx.fillStyle = "white";
    897                     ctx.fill();
    898                     ctx.strokeStyle = "rgba(255,0,0, 1)";
    899                     ctx.fillStyle = "rgba(255,0,0, 1)";
    900                     ctx.stroke();
    901                     ctx.font = "normal 16px Arial";
    902                     ctx.textAlign = "center";
    903                     ctx.fillText(id, _px, _py + 5);
    904                 }
    905             }
    906         }
    907         if (draw_t) {
    908             drawCurveTControl();
    909         }
    910         if (draw_w) {
    911             drawCurveWControl();
    912         }
    913     }
    914 
    915     function drawCurveTControl() {
    916         ctx.lineWidth = 2;
    917         ctx.strokeStyle = "rgba(0,0,0, 0.3)";
    918         ctx.beginPath();
    919         ctx.rect(screenWidth - 80, 40, 28, screenHeight - 80);
    920         ctx.stroke();
    921         var ty = 40 + curveT * (screenHeight - 80);
    922         ctx.beginPath();
    923         ctx.moveTo(screenWidth - 80, ty);
    924         ctx.lineTo(screenWidth - 85, ty - 5);
    925         ctx.lineTo(screenWidth - 85, ty + 5);
    926         ctx.lineTo(screenWidth - 80, ty);
    927         ctx.fillStyle = "rgba(0,0,0, 0.6)";
    928         ctx.fill();
    929         var num = curveT.toFixed(decimal_places);
    930         ctx.font = "normal 10px Arial";
    931         ctx.textAlign = "left";
    932         ctx.fillText(num, screenWidth - 78, ty);
    933     }
    934 
    935     function drawCurveWControl() {
    936         var w = -1;
    937         var choice = 0;
    938         for (var curves in tests[testIndex]) {
    939             var curve = tests[testIndex][curves];
    940             if (curve.length != 7) {
    941                 continue;
    942             }
    943             if (choice == curveW) {
    944                 w = curve[6];
    945                 break;
    946             }
    947             ++choice;
    948         }
    949         if (w < 0) {
    950             return;
    951         }
    952         ctx.lineWidth = 2;
    953         ctx.strokeStyle = "rgba(0,0,0, 0.3)";
    954         ctx.beginPath();
    955         ctx.rect(screenWidth - 40, 40, 28, screenHeight - 80);
    956         ctx.stroke();
    957         var ty = 40 + w * (screenHeight - 80);
    958         ctx.beginPath();
    959         ctx.moveTo(screenWidth - 40, ty);
    960         ctx.lineTo(screenWidth - 45, ty - 5);
    961         ctx.lineTo(screenWidth - 45, ty + 5);
    962         ctx.lineTo(screenWidth - 40, ty);
    963         ctx.fillStyle = "rgba(0,0,0, 0.6)";
    964         ctx.fill();
    965         var num = w.toFixed(decimal_places);
    966         ctx.font = "normal 10px Arial";
    967         ctx.textAlign = "left";
    968         ctx.fillText(num, screenWidth - 38, ty);
    969     }
    970 
    971     function ptInTControl() {
    972         var e = window.event;
    973         var tgt = e.target || e.srcElement;
    974         var left = tgt.offsetLeft;
    975         var top = tgt.offsetTop;
    976         var x = (e.clientX - left);
    977         var y = (e.clientY - top);
    978         if (x < screenWidth - 80 || x > screenWidth - 50) {
    979             return false;
    980         }
    981         if (y < 40 || y > screenHeight - 80) {
    982             return false;
    983         }
    984         curveT = (y - 40) / (screenHeight - 120);
    985         if (curveT < 0 || curveT > 1) {
    986             throw "stop execution";
    987         }
    988         return true;
    989     }
    990 
    991     function ptInWControl() {
    992         var e = window.event;
    993         var tgt = e.target || e.srcElement;
    994         var left = tgt.offsetLeft;
    995         var top = tgt.offsetTop;
    996         var x = (e.clientX - left);
    997         var y = (e.clientY - top);
    998         if (x < screenWidth - 40 || x > screenWidth - 10) {
    999             return false;
   1000         }
   1001         if (y < 40 || y > screenHeight - 80) {
   1002             return false;
   1003         }
   1004         var w = (y - 40) / (screenHeight - 120);
   1005         if (w < 0 || w > 1) {
   1006             throw "stop execution";
   1007         }
   1008         var choice = 0;
   1009         for (var curves in tests[testIndex]) {
   1010             var curve = tests[testIndex][curves];
   1011             if (curve.length != 7) {
   1012                 continue;
   1013             }
   1014             if (choice == curveW) {
   1015                 curve[6] = w;
   1016                 break;
   1017             }
   1018             ++choice;
   1019         }
   1020         return true;
   1021     }
   1022 
   1023     function drawTop() {
   1024         init(tests[testIndex]);
   1025         redraw();
   1026     }
   1027 
   1028     function redraw() {
   1029         if (focus_on_selection > 0) {
   1030             var focusXmin = focusYmin = Infinity;
   1031             var focusXmax = focusYmax = -Infinity;
   1032             var choice = 0;
   1033             for (var curves in tests[testIndex]) {
   1034                 if (++choice != focus_on_selection) {
   1035                     continue;
   1036                 }
   1037                 var curve = tests[testIndex][curves];
   1038                 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
   1039                 for (var idx = 0; idx < last; idx += 2) {
   1040                     focusXmin = Math.min(focusXmin, curve[idx]);
   1041                     focusXmax = Math.max(focusXmax, curve[idx]);
   1042                     focusYmin = Math.min(focusYmin, curve[idx + 1]);
   1043                     focusYmax = Math.max(focusYmax, curve[idx + 1]);
   1044                 }
   1045             }
   1046             focusXmin -= Math.min(1, Math.max(focusXmax - focusXmin, focusYmax - focusYmin));
   1047             if (focusXmin < focusXmax && focusYmin < focusYmax) {
   1048                 setScale(focusXmin, focusXmax, focusYmin, focusYmax);
   1049             }
   1050         }
   1051         ctx.beginPath();
   1052         ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
   1053         ctx.fillStyle = "white";
   1054         ctx.fill();
   1055         draw(tests[testIndex], testTitles[testIndex]);
   1056     }
   1057 
   1058     function doKeyPress(evt) {
   1059         var char = String.fromCharCode(evt.charCode);
   1060         var focusWasOn = false;
   1061         switch (char) {
   1062             case '0':
   1063             case '1':
   1064             case '2':
   1065             case '3':
   1066             case '4':
   1067             case '5':
   1068             case '6':
   1069             case '7':
   1070             case '8':
   1071             case '9':
   1072                 decimal_places = char - '0';
   1073                 redraw();
   1074                 break;
   1075             case '-':
   1076                 focusWasOn = focus_on_selection;
   1077                 if (focusWasOn) {
   1078                     focus_on_selection = false;
   1079                     scale /= 1.2;
   1080                 } else {
   1081                     scale /= 2;
   1082                 }
   1083                 calcLeftTop();
   1084                 redraw();
   1085                 focus_on_selection = focusWasOn;
   1086                 break;
   1087             case '=':
   1088             case '+':
   1089                 focusWasOn = focus_on_selection;
   1090                 if (focusWasOn) {
   1091                     focus_on_selection = false;
   1092                     scale *= 1.2;
   1093                 } else {
   1094                     scale *= 2;
   1095                 }
   1096                 calcLeftTop();
   1097                 redraw();
   1098                 focus_on_selection = focusWasOn;
   1099                 break;
   1100             case 'b':
   1101                 draw_cubic_red ^= true;
   1102                 redraw();
   1103                 break;
   1104             case 'c':
   1105                 drawTop();
   1106                 break;
   1107             case 'd':
   1108                 var test = tests[testIndex];
   1109                 var testClone = [];
   1110                 for (var curves in test) {
   1111                     var c = test[curves];
   1112                     var cClone = [];
   1113                     for (var index = 0; index < c.length; ++index) {
   1114                         cClone.push(c[index]);
   1115                     }
   1116                     testClone.push(cClone);
   1117                 }
   1118                 tests.push(testClone);
   1119                 testTitles.push(testTitles[testIndex] + " copy");
   1120                 testIndex = tests.length - 1;
   1121                 redraw();
   1122                 break;
   1123             case 'e':
   1124                 draw_endpoints = (draw_endpoints + 1) % 3;
   1125                 redraw();
   1126                 break;
   1127             case 'f':
   1128                 draw_derivative ^= true;
   1129                 redraw();
   1130                 break;
   1131             case 'i':
   1132                 draw_ray_intersect = (draw_ray_intersect + 1) % 3;
   1133                 redraw();
   1134                 break;
   1135             case 'l':
   1136                 var test = tests[testIndex];
   1137                 console.log("<div id=\"" + testTitles[testIndex] + "\" >");
   1138                 for (var curves in test) {
   1139                     var c = test[curves];
   1140                     var s = "{{";
   1141                     for (var i = 0; i < c.length; i += 2) {
   1142                         s += "{";
   1143                         s += c[i] + "," + c[i + 1];
   1144                         s += "}";
   1145                         if (i + 2 < c.length) {
   1146                             s += ", ";
   1147                         }
   1148                     }
   1149                     console.log(s + "}},");
   1150                 }
   1151                 console.log("</div>");
   1152                 break;
   1153             case 'm':
   1154                 draw_midpoint = (draw_midpoint + 1) % 3;
   1155                 redraw();
   1156                 break;
   1157             case 'N':
   1158                 testIndex += 9;
   1159             case 'n':
   1160                 testIndex = (testIndex + 1) % tests.length;
   1161                 drawTop();
   1162                 break;
   1163             case 'o':
   1164                 draw_order ^= true;
   1165                 redraw();
   1166                 break;
   1167             case 'P':
   1168                 testIndex -= 9;
   1169             case 'p':
   1170                 if (--testIndex < 0)
   1171                     testIndex = tests.length - 1;
   1172                 drawTop();
   1173                 break;
   1174             case 'q':
   1175                 draw_quarterpoint = (draw_quarterpoint + 1) % 3;
   1176                 redraw();
   1177                 break;
   1178             case 'r':
   1179                 for (var i = 0; i < testDivs.length; ++i) {
   1180                     var title = testDivs[i].id.toString();
   1181                     if (title == testTitles[testIndex]) {
   1182                         var str = testDivs[i].firstChild.data;
   1183                         parse(str, title);
   1184                         var original = tests.pop();
   1185                         testTitles.pop();
   1186                         tests[testIndex] = original;
   1187                         break;
   1188                     }
   1189                 }
   1190                 redraw();
   1191                 break;
   1192             case 's':
   1193                 draw_sortpoint = (draw_sortpoint + 1) % 3;
   1194                 redraw();
   1195                 break;
   1196             case 't':
   1197                 draw_t ^= true;
   1198                 redraw();
   1199                 break;
   1200             case 'u':
   1201                 draw_closest_t ^= true;
   1202                 redraw();
   1203                 break;
   1204             case 'v':
   1205                 draw_tangents = (draw_tangents + 1) % 4;
   1206                 redraw();
   1207                 break;
   1208             case 'w':
   1209                 ++curveW;
   1210                 var choice = 0;
   1211                 draw_w = false;
   1212                 for (var curves in tests[testIndex]) {
   1213                     var curve = tests[testIndex][curves];
   1214                     if (curve.length != 7) {
   1215                         continue;
   1216                     }
   1217                     if (choice == curveW) {
   1218                         draw_w = true;
   1219                         break;
   1220                     }
   1221                     ++choice;
   1222                 }
   1223                 if (!draw_w) {
   1224                     curveW = -1;
   1225                 }
   1226                 redraw();
   1227                 break;
   1228             case 'x':
   1229                 draw_point_xy ^= true;
   1230                 redraw();
   1231                 break;
   1232             case 'y':
   1233                 draw_mouse_xy ^= true;
   1234                 redraw();
   1235                 break;
   1236             case '\\':
   1237                 retina_scale ^= true;
   1238                 drawTop();
   1239                 break;
   1240             case '`':
   1241                 ++focus_on_selection;
   1242                 if (focus_on_selection >= tests[testIndex].length) {
   1243                     focus_on_selection = 0;
   1244                 }
   1245                 setScale(xmin, xmax, ymin, ymax);
   1246                 redraw();
   1247                 break;
   1248             case '.':
   1249                 draw_id = (draw_id + 1) % 3;
   1250                 redraw();
   1251                 break;
   1252         }
   1253     }
   1254 
   1255     function doKeyDown(evt) {
   1256         var char = evt.keyCode;
   1257         var preventDefault = false;
   1258         switch (char) {
   1259             case 37: // left arrow
   1260                 if (evt.shiftKey) {
   1261                     testIndex -= 9;
   1262                 }
   1263                 if (--testIndex < 0)
   1264                     testIndex = tests.length - 1;
   1265                 if (evt.ctrlKey) {
   1266                     redraw();
   1267                 } else {
   1268                     drawTop();
   1269                 }
   1270                 preventDefault = true;
   1271                 break;
   1272             case 39: // right arrow
   1273                 if (evt.shiftKey) {
   1274                     testIndex += 9;
   1275                 }
   1276                 if (++testIndex >= tests.length)
   1277                     testIndex = 0;
   1278                 if (evt.ctrlKey) {
   1279                     redraw();
   1280                 } else {
   1281                     drawTop();
   1282                 }
   1283                 preventDefault = true;
   1284                 break;
   1285         }
   1286         if (preventDefault) {
   1287             evt.preventDefault();
   1288             return false;
   1289         }
   1290         return true;
   1291     }
   1292 
   1293     function calcXY() {
   1294         var e = window.event;
   1295         var tgt = e.target || e.srcElement;
   1296         var left = tgt.offsetLeft;
   1297         var top = tgt.offsetTop;
   1298         mouseX = (e.clientX - left) / scale + srcLeft;
   1299         mouseY = (e.clientY - top) / scale + srcTop;
   1300     }
   1301 
   1302     function calcLeftTop() {
   1303         srcLeft = mouseX - screenWidth / 2 / scale;
   1304         srcTop = mouseY - screenHeight / 2 / scale;
   1305     }
   1306 
   1307     function handleMouseClick() {
   1308         if ((!draw_t || !ptInTControl()) && (!draw_w || !ptInWControl())) {
   1309             calcXY();
   1310         } else {
   1311             redraw();
   1312         }
   1313     }
   1314 
   1315     function initDown() {
   1316         var test = tests[testIndex];
   1317         var bestDistance = 1000000;
   1318         activePt = -1;
   1319         for (var curves in test) {
   1320             var testCurve = test[curves];
   1321             if (testCurve.length != 4 && (testCurve.length < 6 || testCurve.length > 8)) {
   1322                 continue;
   1323             }
   1324             var testMax = testCurve.length == 7 ? 6 : testCurve.length;
   1325             for (var i = 0; i < testMax; i += 2) {
   1326                 var testX = testCurve[i];
   1327                 var testY = testCurve[i + 1];
   1328                 var dx = testX - mouseX;
   1329                 var dy = testY - mouseY;
   1330                 var dist = dx * dx + dy * dy;
   1331                 if (dist > bestDistance) {
   1332                     continue;
   1333                 }
   1334                 activeCurve = testCurve;
   1335                 activePt = i;
   1336                 bestDistance = dist;
   1337             }
   1338         }
   1339         if (activePt >= 0) {
   1340             lastX = mouseX;
   1341             lastY = mouseY;
   1342         }
   1343     }
   1344 
   1345     function handleMouseOver() {
   1346         calcXY();
   1347         if (draw_mouse_xy) {
   1348             var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places);
   1349             ctx.beginPath();
   1350             ctx.rect(300, 100, num.length * 6, 10);
   1351             ctx.fillStyle = "white";
   1352             ctx.fill();
   1353             ctx.font = "normal 10px Arial";
   1354             ctx.fillStyle = "black";
   1355             ctx.textAlign = "left";
   1356             ctx.fillText(num, 300, 108);
   1357         }
   1358         if (!mouseDown) {
   1359             activePt = -1;
   1360             return;
   1361         }
   1362         if (activePt < 0) {
   1363             initDown();
   1364             return;
   1365         }
   1366         var deltaX = mouseX - lastX;
   1367         var deltaY = mouseY - lastY;
   1368         lastX = mouseX;
   1369         lastY = mouseY;
   1370         if (activePt == 0) {
   1371             var test = tests[testIndex];
   1372             for (var curves in test) {
   1373                 var testCurve = test[curves];
   1374                 testCurve[0] += deltaX;
   1375                 testCurve[1] += deltaY;
   1376             }
   1377         } else {
   1378             activeCurve[activePt] += deltaX;
   1379             activeCurve[activePt + 1] += deltaY;
   1380         }
   1381         redraw();
   1382     }
   1383 
   1384     function start() {
   1385         for (var i = 0; i < testDivs.length; ++i) {
   1386             var title = testDivs[i].id.toString();
   1387             var str = testDivs[i].firstChild.data;
   1388             parse(str, title);
   1389         }
   1390         drawTop();
   1391         window.addEventListener('keypress', doKeyPress, true);
   1392         window.addEventListener('keydown', doKeyDown, true);
   1393         window.onresize = function () {
   1394             drawTop();
   1395         }
   1396     }
   1397 
   1398 </script>
   1399 </head>
   1400 
   1401 <body onLoad="start();">
   1402 
   1403 <canvas id="canvas" width="750" height="500"
   1404     onmousedown="mouseDown = true"
   1405     onmouseup="mouseDown = false"
   1406     onmousemove="handleMouseOver()"
   1407     onclick="handleMouseClick()"
   1408     ></canvas >
   1409 </body>
   1410 </html>