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