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