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