3 <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
5 <meta charset="utf-8" />
10 {{{fX=124.70011901855469 fY=9.3718261718750000 } {fX=124.66775026544929 fY=9.3744316215161234 } {fX=124.63530969619751 fY=9.3770743012428284 }{fX=124.60282897949219 fY=9.3797206878662109 } id=10
\r
11 {{{fX=124.70011901855469 fY=9.3718004226684570 } {fX=124.66775026544929 fY=9.3744058723095804 } {fX=124.63530969619751 fY=9.3770485520362854 } {fX=124.60282897949219 fY=9.3796949386596680 } id=1
\r
12 {{{fX=124.70011901855469 fY=9.3718004226684570 } {fX=124.66786243087600 fY=9.3743968522034287 } {fX=124.63553249625420 fY=9.3770303056986286 } {fX=124.60316467285156 fY=9.3796672821044922 } id=2
\r
17 <script type="text/javascript">
23 var decimal_places = 3;
31 var xmin, xmax, ymin, ymax;
33 var hinitScale, vinitScale;
34 var uniformScale = true;
36 var mouseDown = false;
38 var screenWidth, screenHeight;
48 var focus_on_selection = 0;
51 var draw_closest_t = false;
52 var draw_cubic_red = false;
53 var draw_derivative = false;
54 var draw_endpoints = 2;
56 var draw_midpoint = 0;
57 var draw_mouse_xy = false;
58 var draw_order = false;
59 var draw_point_xy = false;
60 var draw_ray_intersect = false;
61 var draw_quarterpoint = 0;
62 var draw_tangents = 1;
63 var draw_sortpoint = 0;
64 var retina_scale = !!window.devicePixelRatio;
66 function parse(test, title) {
67 var curveStrs = test.split("{{");
68 var pattern = /-?\d+\.*\d*e?-?\d*/g;
70 for (var c in curveStrs) {
71 var curveStr = curveStrs[c];
72 var idPart = curveStr.split("id=");
74 if (idPart.length == 2) {
75 id = parseInt(idPart[1]);
78 var points = curveStr.match(pattern);
80 for (var wd in points) {
81 var num = parseFloat(points[wd]);
82 if (isNaN(num)) continue;
93 if (curves.length >= 1) {
95 testTitles.push(title);
100 var canvas = document.getElementById('canvas');
101 if (!canvas.getContext) return;
102 ctx = canvas.getContext('2d');
103 var resScale = retina_scale && window.devicePixelRatio ? window.devicePixelRatio : 1;
104 var unscaledWidth = window.innerWidth - 20;
105 var unscaledHeight = window.innerHeight - 20;
106 screenWidth = unscaledWidth;
107 screenHeight = unscaledHeight;
108 canvas.width = unscaledWidth * resScale;
109 canvas.height = unscaledHeight * resScale;
110 canvas.style.width = unscaledWidth + 'px';
111 canvas.style.height = unscaledHeight + 'px';
113 ctx.scale(resScale, resScale);
119 for (var curves in test) {
120 var curve = test[curves];
121 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
122 for (var idx = 0; idx < last; idx += 2) {
123 xmin = Math.min(xmin, curve[idx]);
124 xmax = Math.max(xmax, curve[idx]);
125 ymin = Math.min(ymin, curve[idx + 1]);
126 ymax = Math.max(ymax, curve[idx + 1]);
129 xmin -= Math.min(1, Math.max(xmax - xmin, ymax - ymin));
130 var testW = xmax - xmin;
131 var testH = ymax - ymin;
133 while (testW * subscale < 0.1 && testH * subscale < 0.1) {
136 while (testW * subscale > 10 && testH * subscale > 10) {
139 setScale(xmin, xmax, ymin, ymax);
140 mouseX = (screenWidth / 2) / hscale + srcLeft;
141 mouseY = (screenHeight / 2) / vscale + srcTop;
146 function setScale(x0, x1, y0, y1) {
147 var srcWidth = x1 - x0;
148 var srcHeight = y1 - y0;
149 var usableWidth = screenWidth;
150 var xDigits = Math.ceil(Math.log(Math.abs(xmax)) / Math.log(10));
151 var yDigits = Math.ceil(Math.log(Math.abs(ymax)) / Math.log(10));
152 usableWidth -= (xDigits + yDigits) * 10;
153 usableWidth -= decimal_places * 10;
154 hscale = usableWidth / srcWidth;
155 vscale = screenHeight / srcHeight;
157 hscale = Math.min(hscale, vscale);
160 var hinvScale = 1 / hscale;
161 var vinvScale = 1 / vscale;
162 var sxmin = x0 - hinvScale * 5;
163 var symin = y0 - vinvScale * 10;
164 var sxmax = x1 + hinvScale * (6 * decimal_places + 10);
165 var symax = y1 + vinvScale * 10;
166 srcWidth = sxmax - sxmin;
167 srcHeight = symax - symin;
168 hscale = usableWidth / srcWidth;
169 vscale = screenHeight / srcHeight;
171 hscale = Math.min(hscale, vscale);
178 function dxy_at_t(curve, t) {
180 if (curve.length == 6) {
184 dxy.x = a * curve[0] + b * curve[2] + c * curve[4];
185 dxy.y = a * curve[1] + b * curve[3] + c * curve[5];
186 } else if (curve.length == 7) {
187 var p20x = curve[4] - curve[0];
188 var p20y = curve[5] - curve[1];
189 var p10xw = (curve[2] - curve[0]) * curve[6];
190 var p10yw = (curve[3] - curve[1]) * curve[6];
191 var coeff0x = curve[6] * p20x - p20x;
192 var coeff0y = curve[6] * p20y - p20y;
193 var coeff1x = p20x - 2 * p10xw;
194 var coeff1y = p20y - 2 * p10yw;
195 dxy.x = t * (t * coeff0x + coeff1x) + p10xw;
196 dxy.y = t * (t * coeff0y + coeff1y) + p10yw;
197 } else if (curve.length == 8) {
203 dxy.x = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
208 dxy.y = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
213 var flt_epsilon = 1.19209290E-07;
215 function approximately_zero(A) {
216 return Math.abs(A) < flt_epsilon;
219 function approximately_zero_inverse(A) {
220 return Math.abs(A) > (1 / flt_epsilon);
223 function quad_real_roots(A, B, C) {
227 if (approximately_zero(A) && (approximately_zero_inverse(p)
228 || approximately_zero_inverse(q))) {
229 if (approximately_zero(B)) {
238 /* normal form: x^2 + px + q = 0 */
240 if (!approximately_zero(p2 - q) && p2 < q) {
245 sqrt_D = Math.sqrt(p2 - q);
248 var flip = -sqrt_D - p;
249 if (!approximately_zero(s[0] - flip)) {
255 function cubic_real_roots(A, B, C, D) {
256 if (approximately_zero(A)) { // we're just a quadratic
257 return quad_real_roots(B, C, D);
259 if (approximately_zero(D)) { // 0 is one root
260 var s = quad_real_roots(A, B, C);
261 for (var i = 0; i < s.length; ++i) {
262 if (approximately_zero(s[i])) {
269 if (approximately_zero(A + B + C + D)) { // 1 is one root
270 var s = quad_real_roots(A, A + B, -D);
271 for (var i = 0; i < s.length; ++i) {
272 if (approximately_zero(s[i] - 1)) {
285 var Q = (a2 - b * 3) / 9;
286 var R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
289 var R2MinusQ3 = R2 - Q3;
293 if (R2MinusQ3 < 0) { // we have 3 real roots
294 var theta = Math.acos(R / Math.sqrt(Q3));
295 var neg2RootQ = -2 * Math.sqrt(Q);
296 r = neg2RootQ * Math.cos(theta / 3) - adiv3;
298 r = neg2RootQ * Math.cos((theta + 2 * Math.PI) / 3) - adiv3;
299 if (!approximately_zero(roots[0] - r)) {
302 r = neg2RootQ * Math.cos((theta - 2 * Math.PI) / 3) - adiv3;
303 if (!approximately_zero(roots[0] - r) && (roots.length == 1
304 || !approximately_zero(roots[1] - r))) {
307 } else { // we have 1 real root
308 var sqrtR2MinusQ3 = Math.sqrt(R2MinusQ3);
309 var A = Math.abs(R) + sqrtR2MinusQ3;
310 A = Math.pow(A, 1/3);
319 if (approximately_zero(R2 - Q3)) {
321 if (!approximately_zero(roots[0] - r)) {
329 function approximately_zero_or_more(tValue) {
330 return tValue >= -flt_epsilon;
333 function approximately_one_or_less(tValue) {
334 return tValue <= 1 + flt_epsilon;
337 function approximately_less_than_zero(tValue) {
338 return tValue < flt_epsilon;
341 function approximately_greater_than_one(tValue) {
342 return tValue > 1 - flt_epsilon;
345 function add_valid_ts(s) {
348 for (var index = 0; index < s.length; ++index) {
349 var tValue = s[index];
350 if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) {
351 if (approximately_less_than_zero(tValue)) {
353 } else if (approximately_greater_than_one(tValue)) {
356 for (var idx2 = 0; idx2 < t.length; ++idx2) {
357 if (approximately_zero(t[idx2] - tValue)) {
367 function quad_roots(A, B, C) {
368 var s = quad_real_roots(A, B, C);
369 var foundRoots = add_valid_ts(s);
373 function cubic_roots(A, B, C, D) {
374 var s = cubic_real_roots(A, B, C, D);
375 var foundRoots = add_valid_ts(s);
379 function ray_curve_intersect(startPt, endPt, curve) {
380 var adj = endPt[0] - startPt[0];
381 var opp = endPt[1] - startPt[1];
383 var len = (curve.length == 7 ? 6 : curve.length) / 2;
384 for (var n = 0; n < len; ++n) {
385 r[n] = (curve[n * 2 + 1] - startPt[1]) * adj - (curve[n * 2] - startPt[0]) * opp;
387 if (curve.length == 6) {
391 A += C - 2 * B; // A = a - 2*b + c
392 B -= C; // B = -(b - c)
393 return quad_roots(A, 2 * B, C);
395 if (curve.length == 7) {
397 var B = r[1] * curve[6];
399 A += C - 2 * B; // A = a - 2*b + c
400 B -= C; // B = -(b - c)
401 return quad_roots(A, 2 * B, C);
404 var B = r[2] * 3; // 3*c
405 var C = r[1] * 3; // 3*b
407 A -= D - C + B; // A = -a + 3*b - 3*c + d
408 B += 3 * D - 2 * C; // B = 3*a - 6*b + 3*c
409 C -= 3 * D; // C = -3*a + 3*b
410 return cubic_roots(A, B, C, D);
413 function x_at_t(curve, t) {
415 if (curve.length == 4) {
416 return one_t * curve[0] + t * curve[2];
418 var one_t2 = one_t * one_t;
420 if (curve.length == 6) {
421 return one_t2 * curve[0] + 2 * one_t * t * curve[2] + t2 * curve[4];
423 if (curve.length == 7) {
424 var numer = one_t2 * curve[0] + 2 * one_t * t * curve[2] * curve[6]
426 var denom = one_t2 + 2 * one_t * t * curve[6]
428 return numer / denom;
430 var a = one_t2 * one_t;
431 var b = 3 * one_t2 * t;
432 var c = 3 * one_t * t2;
434 return a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6];
437 function y_at_t(curve, t) {
439 if (curve.length == 4) {
440 return one_t * curve[1] + t * curve[3];
442 var one_t2 = one_t * one_t;
444 if (curve.length == 6) {
445 return one_t2 * curve[1] + 2 * one_t * t * curve[3] + t2 * curve[5];
447 if (curve.length == 7) {
448 var numer = one_t2 * curve[1] + 2 * one_t * t * curve[3] * curve[6]
450 var denom = one_t2 + 2 * one_t * t * curve[6]
452 return numer / denom;
454 var a = one_t2 * one_t;
455 var b = 3 * one_t2 * t;
456 var c = 3 * one_t * t2;
458 return a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7];
461 function drawPointAtT(curve) {
462 var x = x_at_t(curve, curveT);
463 var y = y_at_t(curve, curveT);
464 drawPoint(x, y, false);
467 function drawLine(x1, y1, x2, y2) {
469 ctx.moveTo((x1 - srcLeft) * hscale,
470 (y1 - srcTop) * vscale);
471 ctx.lineTo((x2 - srcLeft) * hscale,
472 (y2 - srcTop) * vscale);
476 function drawPoint(px, py, xend) {
477 for (var pts = 0; pts < drawnPts.length; pts += 2) {
478 var x = drawnPts[pts];
479 var y = drawnPts[pts + 1];
480 if (px == x && py == y) {
486 var _px = (px - srcLeft) * hscale;
487 var _py = (py - srcTop) * vscale;
490 ctx.moveTo(_px - 3, _py - 3);
491 ctx.lineTo(_px + 3, _py + 3);
492 ctx.moveTo(_px - 3, _py + 3);
493 ctx.lineTo(_px + 3, _py - 3);
495 ctx.arc(_px, _py, 3, 0, Math.PI * 2, true);
500 var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places);
501 ctx.font = "normal 10px Arial";
502 ctx.textAlign = "left";
503 ctx.fillStyle = "black";
504 ctx.fillText(label, _px + 5, _py);
508 function drawPointSolid(px, py) {
509 drawPoint(px, py, false);
510 ctx.fillStyle = "rgba(0,0,0, 0.4)";
514 function crossPt(origin, pt1, pt2) {
515 return ((pt1[0] - origin[0]) * (pt2[1] - origin[1])
516 - (pt1[1] - origin[1]) * (pt2[0] - origin[0])) > 0 ? 0 : 1;
519 // may not work well for cubics
520 function curveClosestT(curve, x, y) {
522 var closestDist = Infinity;
523 var l = Infinity, t = Infinity, r = -Infinity, b = -Infinity;
524 for (var i = 0; i < 16; ++i) {
525 var testX = x_at_t(curve, i / 16);
526 l = Math.min(testX, l);
527 r = Math.max(testX, r);
528 var testY = y_at_t(curve, i / 16);
529 t = Math.min(testY, t);
530 b = Math.max(testY, b);
533 var dist = dx * dx + dy * dy;
534 if (closestDist > dist) {
541 var boundsDist = boundsX * boundsX + boundsY * boundsY;
542 if (closestDist > boundsDist) {
545 console.log("closestDist = " + closestDist + " boundsDist = " + boundsDist
546 + " t = " + closest / 16);
550 var kMaxConicToQuadPOW2 = 5;
552 function computeQuadPOW2(curve, tol) {
553 var a = curve[6] - 1;
554 var k = a / (4 * (2 + a));
555 var x = k * (curve[0] - 2 * curve[2] + curve[4]);
556 var y = k * (curve[1] - 2 * curve[3] + curve[5]);
558 var error = Math.sqrt(x * x + y * y);
560 for (pow2 = 0; pow2 < kMaxConicToQuadPOW2; ++pow2) {
569 function subdivide_w_value(w) {
570 return Math.sqrt(0.5 + w * 0.5);
573 function chop(curve, part1, part2) {
575 var scale = 1 / (1 + w);
578 part1[2] = (curve[0] + curve[2] * w) * scale;
579 part1[3] = (curve[1] + curve[3] * w) * scale;
580 part1[4] = part2[0] = (curve[0] + (curve[2] * w) * 2 + curve[4]) * scale * 0.5;
581 part1[5] = part2[1] = (curve[1] + (curve[3] * w) * 2 + curve[5]) * scale * 0.5;
582 part2[2] = (curve[2] * w + curve[4]) * scale;
583 part2[3] = (curve[3] * w + curve[5]) * scale;
586 part1[6] = part2[6] = subdivide_w_value(w);
589 function subdivide(curve, level, pts) {
596 var part1 = [], part2 = [];
597 chop(curve, part1, part2);
599 subdivide(part1, level, pts);
600 subdivide(part2, level, pts);
604 function chopIntoQuadsPOW2(curve, pow2, pts) {
605 subdivide(curve, pow2, pts);
609 function drawConic(curve, srcLeft, srcTop, hscale, vscale) {
610 var tol = 1 / Math.min(hscale, vscale);
611 var pow2 = computeQuadPOW2(curve, tol);
613 chopIntoQuadsPOW2(curve, pow2, pts);
614 for (var i = 0; i < pts.length; i += 4) {
615 ctx.quadraticCurveTo(
616 (pts[i + 0] - srcLeft) * hscale, (pts[i + 1] - srcTop) * vscale,
617 (pts[i + 2] - srcLeft) * hscale, (pts[i + 3] - srcTop) * vscale);
621 function draw(test, title) {
622 ctx.font = "normal 50px Arial";
623 ctx.textAlign = "left";
624 ctx.fillStyle = "rgba(0,0,0, 0.1)";
625 ctx.fillText(title, 50, 50);
626 ctx.font = "normal 10px Arial";
627 // ctx.lineWidth = "1.001"; "0.999";
633 var shortSpokes = [];
637 for (var curves in test) {
638 var curve = test[curves];
639 origin.push(curve[0]);
640 origin.push(curve[1]);
642 startPt.push(curve[2]);
643 startPt.push(curve[3]);
644 hullStarts.push(startPt);
646 if (curve.length == 4) {
647 endPt.push(curve[2]);
648 endPt.push(curve[3]);
649 } else if (curve.length == 6 || curve.length == 7) {
650 endPt.push(curve[4]);
651 endPt.push(curve[5]);
652 } else if (curve.length == 8) {
653 endPt.push(curve[6]);
654 endPt.push(curve[7]);
656 hullEnds.push(endPt);
657 var sweep = crossPt(origin, startPt, endPt);
660 midPt.push(x_at_t(curve, 0.5));
661 midPt.push(y_at_t(curve, 0.5));
662 midSpokes.push(midPt);
664 shortPt.push(x_at_t(curve, 0.25));
665 shortPt.push(y_at_t(curve, 0.25));
666 shortSpokes.push(shortPt);
667 var dx = midPt[0] - origin[0];
668 var dy = midPt[1] - origin[1];
669 var dist = Math.sqrt(dx * dx + dy * dy);
671 dx = shortPt[0] - origin[0];
672 dy = shortPt[1] - origin[1];
673 dist = Math.sqrt(dx * dx + dy * dy);
674 shortDist.push(dist);
677 var useIntersect = false;
678 var maxWidth = Math.max(xmax - xmin, ymax - ymin);
679 for (var curves in test) {
680 var curve = test[curves];
681 if (curve.length >= 6 && curve.length <= 8) {
682 var opp = curves == 0 || curves == 1 ? 0 : 1;
683 var sects = ray_curve_intersect(origin, hullEnds[opp], curve);
684 intersect.push(sects);
685 if (sects.length > 1) {
686 var intersection = sects[0];
687 if (intersection == 0) {
688 intersection = sects[1];
690 var ix = x_at_t(curve, intersection) - origin[0];
691 var iy = y_at_t(curve, intersection) - origin[1];
692 var ex = hullEnds[opp][0] - origin[0];
693 var ey = hullEnds[opp][1] - origin[1];
694 if (ix * ex >= 0 && iy * ey >= 0) {
695 var iDist = Math.sqrt(ix * ix + iy * iy);
696 var eDist = Math.sqrt(ex * ex + ey * ey);
697 var delta = Math.abs(iDist - eDist) / maxWidth;
698 if (delta > (curve.length != 8 ? 1e-5 : 1e-4)) {
699 useIntersect ^= true;
705 var midLeft = curves != 0 ? crossPt(origin, midSpokes[0], midSpokes[1]) : 0;
708 var sect1 = intersect[0].length > 1;
709 var sIndex = sect1 ? 0 : 1;
710 var sects = intersect[sIndex];
711 var intersection = sects[0];
712 if (intersection == 0) {
713 intersection = sects[1];
715 var curve = test[sIndex];
716 var ix = x_at_t(curve, intersection) - origin[0];
717 var iy = y_at_t(curve, intersection) - origin[1];
718 var opp = sect1 ? 1 : 0;
719 var ex = hullEnds[opp][0] - origin[0];
720 var ey = hullEnds[opp][1] - origin[1];
721 var iDist = ix * ix + iy * iy;
722 var eDist = ex * ex + ey * ey;
723 firstInside = (iDist > eDist) ^ (sIndex == 0) ^ sweeps[0];
724 // console.log("iDist=" + iDist + " eDist=" + eDist + " sIndex=" + sIndex
725 // + " sweeps[0]=" + sweeps[0]);
727 // console.log("midLeft=" + midLeft);
728 firstInside = midLeft != 0;
730 var shorter = midDist[1] < midDist[0];
731 var shortLeft = shorter ? crossPt(origin, shortSpokes[0], midSpokes[1])
732 : crossPt(origin, midSpokes[0], shortSpokes[1]);
733 var startCross = crossPt(origin, hullStarts[0], hullStarts[1]);
734 var disallowShort = midLeft == startCross && midLeft == sweeps[0]
735 && midLeft == sweeps[1];
737 // console.log("midLeft=" + midLeft + " startCross=" + startCross);
738 var intersectIndex = 0;
739 for (var curves in test) {
740 var curve = test[draw_id != 2 ? curves : test.length - curves - 1];
741 if (curve.length != 4 && curve.length != 6 && curve.length != 7 && curve.length != 8) {
745 if (draw_tangents != 0) {
746 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
747 ctx.strokeStyle = "rgba(255,0,0, 0.3)";
749 ctx.strokeStyle = "rgba(0,0,255, 0.3)";
751 drawLine(curve[0], curve[1], curve[2], curve[3]);
752 if (draw_tangents != 2) {
753 if (curve.length > 4) drawLine(curve[2], curve[3], curve[4], curve[5]);
754 if (curve.length == 8) drawLine(curve[4], curve[5], curve[6], curve[7]);
756 if (draw_tangents != 1) {
757 if (curve.length == 6 || curve.length == 7) {
758 drawLine(curve[0], curve[1], curve[4], curve[5]);
760 if (curve.length == 8) drawLine(curve[0], curve[1], curve[6], curve[7]);
764 ctx.moveTo((curve[0] - srcLeft) * hscale, (curve[1] - srcTop) * vscale);
765 if (curve.length == 4) {
766 ctx.lineTo((curve[2] - srcLeft) * hscale, (curve[3] - srcTop) * vscale);
767 } else if (curve.length == 6) {
768 ctx.quadraticCurveTo(
769 (curve[2] - srcLeft) * hscale, (curve[3] - srcTop) * vscale,
770 (curve[4] - srcLeft) * hscale, (curve[5] - srcTop) * vscale);
771 } else if (curve.length == 7) {
772 drawConic(curve, srcLeft, srcTop, hscale, vscale);
775 (curve[2] - srcLeft) * hscale, (curve[3] - srcTop) * vscale,
776 (curve[4] - srcLeft) * hscale, (curve[5] - srcTop) * vscale,
777 (curve[6] - srcLeft) * hscale, (curve[7] - srcTop) * vscale);
779 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
780 ctx.strokeStyle = "rgba(255,0,0, 1)";
782 ctx.strokeStyle = "rgba(0,0,255, 1)";
785 if (draw_endpoints > 0) {
786 drawPoint(curve[0], curve[1], false);
787 if (draw_endpoints > 1 || curve.length == 4) {
788 drawPoint(curve[2], curve[3], curve.length == 4 && draw_endpoints == 3);
790 if (curve.length == 6 || curve.length == 7 ||
791 (draw_endpoints > 1 && curve.length == 8)) {
792 drawPoint(curve[4], curve[5], (curve.length == 6 || curve.length == 7) && draw_endpoints == 3);
794 if (curve.length == 8) {
795 drawPoint(curve[6], curve[7], curve.length == 8 && draw_endpoints == 3);
798 if (draw_midpoint != 0) {
799 if ((curves == 0) == (midLeft == 0)) {
800 ctx.strokeStyle = "rgba(0,180,127, 0.6)";
802 ctx.strokeStyle = "rgba(127,0,127, 0.6)";
804 var midX = x_at_t(curve, 0.5);
805 var midY = y_at_t(curve, 0.5);
806 drawPointSolid(midX, midY);
807 if (draw_midpoint > 1) {
808 drawLine(curve[0], curve[1], midX, midY);
811 if (draw_quarterpoint != 0) {
812 if ((curves == 0) == (shortLeft == 0)) {
813 ctx.strokeStyle = "rgba(0,191,63, 0.6)";
815 ctx.strokeStyle = "rgba(63,0,191, 0.6)";
817 var midT = (curves == 0) == shorter ? 0.25 : 0.5;
818 var midX = x_at_t(curve, midT);
819 var midY = y_at_t(curve, midT);
820 drawPointSolid(midX, midY);
821 if (draw_quarterpoint > 1) {
822 drawLine(curve[0], curve[1], midX, midY);
825 if (draw_sortpoint != 0) {
826 if ((curves == 0) == ((disallowShort == -1 ? midLeft : shortLeft) == 0)) {
827 ctx.strokeStyle = "rgba(0,155,37, 0.6)";
829 ctx.strokeStyle = "rgba(37,0,155, 0.6)";
831 var midT = (curves == 0) == shorter && disallowShort != curves ? 0.25 : 0.5;
832 console.log("curves=" + curves + " disallowShort=" + disallowShort
833 + " midLeft=" + midLeft + " shortLeft=" + shortLeft
834 + " shorter=" + shorter + " midT=" + midT);
835 var midX = x_at_t(curve, midT);
836 var midY = y_at_t(curve, midT);
837 drawPointSolid(midX, midY);
838 if (draw_sortpoint > 1) {
839 drawLine(curve[0], curve[1], midX, midY);
842 if (draw_ray_intersect != 0) {
843 ctx.strokeStyle = "rgba(75,45,199, 0.6)";
844 if (curve.length >= 6 && curve.length <= 8) {
845 var intersections = intersect[intersectIndex];
846 for (var i in intersections) {
847 var intersection = intersections[i];
848 var x = x_at_t(curve, intersection);
849 var y = y_at_t(curve, intersection);
850 drawPointSolid(x, y);
851 if (draw_ray_intersect > 1) {
852 drawLine(curve[0], curve[1], x, y);
859 var px = x_at_t(curve, 0.75);
860 var py = y_at_t(curve, 0.75);
861 var _px = (px - srcLeft) * hscale;
862 var _py = (py - srcTop) * vscale;
864 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
866 ctx.fillStyle = "white";
868 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
869 ctx.strokeStyle = "rgba(255,0,0, 1)";
870 ctx.fillStyle = "rgba(255,0,0, 1)";
872 ctx.strokeStyle = "rgba(0,0,255, 1)";
873 ctx.fillStyle = "rgba(0,0,255, 1)";
876 ctx.font = "normal 16px Arial";
877 ctx.textAlign = "center";
878 ctx.fillText(parseInt(curves) + 1, _px, _py + 5);
880 if (draw_closest_t) {
881 var t = curveClosestT(curve, mouseX, mouseY);
883 var x = x_at_t(curve, t);
884 var y = y_at_t(curve, t);
885 drawPointSolid(x, y);
888 if (!approximately_zero(hscale - hinitScale)) {
889 ctx.font = "normal 20px Arial";
890 ctx.fillStyle = "rgba(0,0,0, 0.3)";
891 ctx.textAlign = "right";
892 var scaleTextOffset = hscale != vscale ? -25 : -5;
893 ctx.fillText(hscale.toFixed(decimal_places) + 'x',
894 screenWidth - 10, screenHeight - scaleTextOffset);
895 if (hscale != vscale) {
896 ctx.fillText(vscale.toFixed(decimal_places) + 'y',
897 screenWidth - 10, screenHeight - 5);
905 for (var i = 0; i < ids.length; i += 2) {
906 if (ids[i + 1] == curve) {
912 var px = x_at_t(curve, 0.5);
913 var py = y_at_t(curve, 0.5);
914 var _px = (px - srcLeft) * hscale;
915 var _py = (py - srcTop) * vscale;
917 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
919 ctx.fillStyle = "white";
921 ctx.strokeStyle = "rgba(255,0,0, 1)";
922 ctx.fillStyle = "rgba(255,0,0, 1)";
924 ctx.font = "normal 16px Arial";
925 ctx.textAlign = "center";
926 ctx.fillText(id, _px, _py + 5);
938 function drawCurveTControl() {
940 ctx.strokeStyle = "rgba(0,0,0, 0.3)";
942 ctx.rect(screenWidth - 80, 40, 28, screenHeight - 80);
944 var ty = 40 + curveT * (screenHeight - 80);
946 ctx.moveTo(screenWidth - 80, ty);
947 ctx.lineTo(screenWidth - 85, ty - 5);
948 ctx.lineTo(screenWidth - 85, ty + 5);
949 ctx.lineTo(screenWidth - 80, ty);
950 ctx.fillStyle = "rgba(0,0,0, 0.6)";
952 var num = curveT.toFixed(decimal_places);
953 ctx.font = "normal 10px Arial";
954 ctx.textAlign = "left";
955 ctx.fillText(num, screenWidth - 78, ty);
958 function drawCurveWControl() {
961 for (var curves in tests[testIndex]) {
962 var curve = tests[testIndex][curves];
963 if (curve.length != 7) {
966 if (choice == curveW) {
976 ctx.strokeStyle = "rgba(0,0,0, 0.3)";
978 ctx.rect(screenWidth - 40, 40, 28, screenHeight - 80);
980 var ty = 40 + w * (screenHeight - 80);
982 ctx.moveTo(screenWidth - 40, ty);
983 ctx.lineTo(screenWidth - 45, ty - 5);
984 ctx.lineTo(screenWidth - 45, ty + 5);
985 ctx.lineTo(screenWidth - 40, ty);
986 ctx.fillStyle = "rgba(0,0,0, 0.6)";
988 var num = w.toFixed(decimal_places);
989 ctx.font = "normal 10px Arial";
990 ctx.textAlign = "left";
991 ctx.fillText(num, screenWidth - 38, ty);
994 function ptInTControl() {
995 var e = window.event;
996 var tgt = e.target || e.srcElement;
997 var left = tgt.offsetLeft;
998 var top = tgt.offsetTop;
999 var x = (e.clientX - left);
1000 var y = (e.clientY - top);
1001 if (x < screenWidth - 80 || x > screenWidth - 50) {
1004 if (y < 40 || y > screenHeight - 80) {
1007 curveT = (y - 40) / (screenHeight - 120);
1008 if (curveT < 0 || curveT > 1) {
1009 throw "stop execution";
1014 function ptInWControl() {
1015 var e = window.event;
1016 var tgt = e.target || e.srcElement;
1017 var left = tgt.offsetLeft;
1018 var top = tgt.offsetTop;
1019 var x = (e.clientX - left);
1020 var y = (e.clientY - top);
1021 if (x < screenWidth - 40 || x > screenWidth - 10) {
1024 if (y < 40 || y > screenHeight - 80) {
1027 var w = (y - 40) / (screenHeight - 120);
1028 if (w < 0 || w > 1) {
1029 throw "stop execution";
1032 for (var curves in tests[testIndex]) {
1033 var curve = tests[testIndex][curves];
1034 if (curve.length != 7) {
1037 if (choice == curveW) {
1046 function drawTop() {
1047 init(tests[testIndex]);
1052 if (focus_on_selection > 0) {
1053 var focusXmin = focusYmin = Infinity;
1054 var focusXmax = focusYmax = -Infinity;
1056 for (var curves in tests[testIndex]) {
1057 if (++choice != focus_on_selection) {
1060 var curve = tests[testIndex][curves];
1061 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
1062 for (var idx = 0; idx < last; idx += 2) {
1063 focusXmin = Math.min(focusXmin, curve[idx]);
1064 focusXmax = Math.max(focusXmax, curve[idx]);
1065 focusYmin = Math.min(focusYmin, curve[idx + 1]);
1066 focusYmax = Math.max(focusYmax, curve[idx + 1]);
1069 focusXmin -= Math.min(1, Math.max(focusXmax - focusXmin, focusYmax - focusYmin));
1070 if (focusXmin < focusXmax && focusYmin < focusYmax) {
1071 setScale(focusXmin, focusXmax, focusYmin, focusYmax);
1075 ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
1076 ctx.fillStyle = "white";
1078 draw(tests[testIndex], testTitles[testIndex]);
1081 function doKeyPress(evt) {
1082 var char = String.fromCharCode(evt.charCode);
1083 var focusWasOn = false;
1095 decimal_places = char - '0';
1099 focusWasOn = focus_on_selection;
1101 focus_on_selection = false;
1110 focus_on_selection = focusWasOn;
1114 focusWasOn = focus_on_selection;
1116 focus_on_selection = false;
1125 focus_on_selection = focusWasOn;
1128 draw_cubic_red ^= true;
1135 var test = tests[testIndex];
1137 for (var curves in test) {
1138 var c = test[curves];
1140 for (var index = 0; index < c.length; ++index) {
1141 cClone.push(c[index]);
1143 testClone.push(cClone);
1145 tests.push(testClone);
1146 testTitles.push(testTitles[testIndex] + " copy");
1147 testIndex = tests.length - 1;
1151 draw_endpoints = (draw_endpoints + 1) % 4;
1155 draw_derivative ^= true;
1179 draw_ray_intersect = (draw_ray_intersect + 1) % 3;
1183 var test = tests[testIndex];
1184 console.log("<div id=\"" + testTitles[testIndex] + "\" >");
1185 for (var curves in test) {
1186 var c = test[curves];
1188 for (var i = 0; i < c.length; i += 2) {
1190 s += c[i] + "," + c[i + 1];
1192 if (i + 2 < c.length) {
1196 console.log(s + "}},");
1198 console.log("</div>");
1201 draw_midpoint = (draw_midpoint + 1) % 3;
1207 testIndex = (testIndex + 1) % tests.length;
1217 if (--testIndex < 0)
1218 testIndex = tests.length - 1;
1222 draw_quarterpoint = (draw_quarterpoint + 1) % 3;
1226 for (var i = 0; i < testDivs.length; ++i) {
1227 var title = testDivs[i].id.toString();
1228 if (title == testTitles[testIndex]) {
1229 var str = testDivs[i].firstChild.data;
1231 var original = tests.pop();
1233 tests[testIndex] = original;
1240 draw_sortpoint = (draw_sortpoint + 1) % 3;
1248 draw_closest_t ^= true;
1252 draw_tangents = (draw_tangents + 1) % 4;
1259 for (var curves in tests[testIndex]) {
1260 var curve = tests[testIndex][curves];
1261 if (curve.length != 7) {
1264 if (choice == curveW) {
1276 draw_point_xy ^= true;
1280 draw_mouse_xy ^= true;
1284 retina_scale ^= true;
1288 ++focus_on_selection;
1289 if (focus_on_selection >= tests[testIndex].length) {
1290 focus_on_selection = 0;
1292 setScale(xmin, xmax, ymin, ymax);
1296 draw_id = (draw_id + 1) % 3;
1302 function doKeyDown(evt) {
1303 var char = evt.keyCode;
1304 var preventDefault = false;
1306 case 37: // left arrow
1310 if (--testIndex < 0)
1311 testIndex = tests.length - 1;
1317 preventDefault = true;
1319 case 39: // right arrow
1323 if (++testIndex >= tests.length)
1330 preventDefault = true;
1333 if (preventDefault) {
1334 evt.preventDefault();
1341 var e = window.event;
1342 var tgt = e.target || e.srcElement;
1343 var left = tgt.offsetLeft;
1344 var top = tgt.offsetTop;
1345 mouseX = (e.clientX - left) / hscale + srcLeft;
1346 mouseY = (e.clientY - top) / vscale + srcTop;
1349 function calcLeftTop() {
1350 srcLeft = mouseX - screenWidth / 2 / hscale;
1351 srcTop = mouseY - screenHeight / 2 / vscale;
1354 function handleMouseClick() {
1355 if ((!draw_t || !ptInTControl()) && (!draw_w || !ptInWControl())) {
1362 function initDown() {
1363 var test = tests[testIndex];
1364 var bestDistance = 1000000;
1366 for (var curves in test) {
1367 var testCurve = test[curves];
1368 if (testCurve.length != 4 && (testCurve.length < 6 || testCurve.length > 8)) {
1371 var testMax = testCurve.length == 7 ? 6 : testCurve.length;
1372 for (var i = 0; i < testMax; i += 2) {
1373 var testX = testCurve[i];
1374 var testY = testCurve[i + 1];
1375 var dx = testX - mouseX;
1376 var dy = testY - mouseY;
1377 var dist = dx * dx + dy * dy;
1378 if (dist > bestDistance) {
1381 activeCurve = testCurve;
1383 bestDistance = dist;
1386 if (activePt >= 0) {
1392 function handleMouseOver() {
1394 if (draw_mouse_xy) {
1395 var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places);
1397 ctx.rect(300, 100, num.length * 6, 10);
1398 ctx.fillStyle = "white";
1400 ctx.font = "normal 10px Arial";
1401 ctx.fillStyle = "black";
1402 ctx.textAlign = "left";
1403 ctx.fillText(num, 300, 108);
1413 var deltaX = mouseX - lastX;
1414 var deltaY = mouseY - lastY;
1417 if (activePt == 0) {
1418 var test = tests[testIndex];
1419 for (var curves in test) {
1420 var testCurve = test[curves];
1421 testCurve[0] += deltaX;
1422 testCurve[1] += deltaY;
1425 activeCurve[activePt] += deltaX;
1426 activeCurve[activePt + 1] += deltaY;
1432 for (var i = 0; i < testDivs.length; ++i) {
1433 var title = testDivs[i].id.toString();
1434 var str = testDivs[i].firstChild.data;
1438 window.addEventListener('keypress', doKeyPress, true);
1439 window.addEventListener('keydown', doKeyDown, true);
1440 window.onresize = function () {
1448 <body onLoad="start();">
1450 <canvas id="canvas" width="750" height="500"
1451 onmousedown="mouseDown = true"
1452 onmouseup="mouseDown = false"
1453 onmousemove="handleMouseOver()"
1454 onclick="handleMouseClick()"