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