1457 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			HTML
		
	
	
	
			
		
		
	
	
			1457 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			HTML
		
	
	
	
| <!DOCTYPE html>
 | |
| 
 | |
| <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
 | |
| <head>
 | |
|     <meta charset="utf-8" />
 | |
|     <title></title>
 | |
| <div style="height:0">
 | |
| 
 | |
|     <div id="cubics">
 | |
| {{{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      
 | |
| {{{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
 | |
| {{{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
 | |
|     </div>
 | |
| 
 | |
|     </div>
 | |
| 
 | |
| <script type="text/javascript">
 | |
| 
 | |
|     var testDivs = [
 | |
|     cubics,
 | |
|     ];
 | |
| 
 | |
|     var decimal_places = 3;
 | |
| 
 | |
|     var tests = [];
 | |
|     var testTitles = [];
 | |
|     var testIndex = 0;
 | |
|     var ctx;
 | |
| 
 | |
|     var subscale = 1;
 | |
|     var xmin, xmax, ymin, ymax;
 | |
|     var hscale, vscale;
 | |
|     var hinitScale, vinitScale;
 | |
|     var uniformScale = true;
 | |
|     var mouseX, mouseY;
 | |
|     var mouseDown = false;
 | |
|     var srcLeft, srcTop;
 | |
|     var screenWidth, screenHeight;
 | |
|     var drawnPts;
 | |
|     var curveT = 0;
 | |
|     var curveW = -1;
 | |
| 
 | |
|     var lastX, lastY;
 | |
|     var activeCurve = [];
 | |
|     var activePt;
 | |
|     var ids = [];
 | |
| 
 | |
|     var focus_on_selection = 0;
 | |
|     var draw_t = false;
 | |
|     var draw_w = false;
 | |
|     var draw_closest_t = false;
 | |
|     var draw_cubic_red = false;
 | |
|     var draw_derivative = false;
 | |
|     var draw_endpoints = 2;
 | |
|     var draw_id = 0;
 | |
|     var draw_midpoint = 0;
 | |
|     var draw_mouse_xy = false;
 | |
|     var draw_order = false;
 | |
|     var draw_point_xy = false;
 | |
|     var draw_ray_intersect = false;
 | |
|     var draw_quarterpoint = 0;
 | |
|     var draw_tangents = 1;
 | |
|     var draw_sortpoint = 0;
 | |
|     var retina_scale = !!window.devicePixelRatio;
 | |
| 
 | |
|     function parse(test, title) {
 | |
|         var curveStrs = test.split("{{");
 | |
|         var pattern = /-?\d+\.*\d*e?-?\d*/g;
 | |
|         var curves = [];
 | |
|         for (var c in curveStrs) {
 | |
|             var curveStr = curveStrs[c];
 | |
|             var idPart = curveStr.split("id=");
 | |
|             var id = -1;
 | |
|             if (idPart.length == 2) {
 | |
|                 id = parseInt(idPart[1]);
 | |
|                 curveStr = idPart[0];
 | |
|             }
 | |
|             var points = curveStr.match(pattern);
 | |
|             var pts = [];
 | |
|             for (var wd in points) {
 | |
|                 var num = parseFloat(points[wd]);
 | |
|                 if (isNaN(num)) continue;
 | |
|                 pts.push(num);
 | |
|             }
 | |
|             if (pts.length > 2) {
 | |
|                 curves.push(pts);
 | |
|             }
 | |
|             if (id >= 0) {
 | |
|                 ids.push(id);
 | |
|                 ids.push(pts);
 | |
|             }
 | |
|         }
 | |
|         if (curves.length >= 1) {
 | |
|             tests.push(curves);
 | |
|             testTitles.push(title);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function init(test) {
 | |
|         var canvas = document.getElementById('canvas');
 | |
|         if (!canvas.getContext) return;
 | |
|         ctx = canvas.getContext('2d');
 | |
|         var resScale = retina_scale && window.devicePixelRatio ? window.devicePixelRatio : 1;
 | |
|         var unscaledWidth = window.innerWidth - 20;
 | |
|         var unscaledHeight = window.innerHeight - 20;
 | |
|         screenWidth = unscaledWidth;
 | |
|         screenHeight = unscaledHeight;
 | |
|         canvas.width = unscaledWidth * resScale;
 | |
|         canvas.height = unscaledHeight * resScale;
 | |
|         canvas.style.width = unscaledWidth + 'px';
 | |
|         canvas.style.height = unscaledHeight + 'px';
 | |
|         if (resScale != 1) {
 | |
|             ctx.scale(resScale, resScale);
 | |
|         }
 | |
|         xmin = Infinity;
 | |
|         xmax = -Infinity;
 | |
|         ymin = Infinity;
 | |
|         ymax = -Infinity;
 | |
|         for (var curves in test) {
 | |
|             var curve = test[curves];
 | |
|             var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
 | |
|             for (var idx = 0; idx < last; idx += 2) {
 | |
|                 xmin = Math.min(xmin, curve[idx]);
 | |
|                 xmax = Math.max(xmax, curve[idx]);
 | |
|                 ymin = Math.min(ymin, curve[idx + 1]);
 | |
|                 ymax = Math.max(ymax, curve[idx + 1]);
 | |
|             }
 | |
|         }
 | |
|         xmin -= Math.min(1, Math.max(xmax - xmin, ymax - ymin));
 | |
|         var testW = xmax - xmin;
 | |
|         var testH = ymax - ymin;
 | |
|         subscale = 1;
 | |
|         while (testW * subscale < 0.1 && testH * subscale < 0.1) {
 | |
|             subscale *= 10;
 | |
|         }
 | |
|         while (testW * subscale > 10 && testH * subscale > 10) {
 | |
|             subscale /= 10;
 | |
|         }
 | |
|         setScale(xmin, xmax, ymin, ymax);
 | |
|         mouseX = (screenWidth / 2) / hscale + srcLeft;
 | |
|         mouseY = (screenHeight / 2) / vscale + srcTop;
 | |
|         hinitScale = hscale;
 | |
|         vinitScale = vscale;
 | |
|     }
 | |
| 
 | |
|     function setScale(x0, x1, y0, y1) {
 | |
|         var srcWidth = x1 - x0;
 | |
|         var srcHeight = y1 - y0;
 | |
|         var usableWidth = screenWidth;
 | |
|         var xDigits = Math.ceil(Math.log(Math.abs(xmax)) / Math.log(10));
 | |
|         var yDigits = Math.ceil(Math.log(Math.abs(ymax)) / Math.log(10));
 | |
|         usableWidth -= (xDigits + yDigits) * 10;
 | |
|         usableWidth -= decimal_places * 10;
 | |
|         hscale = usableWidth / srcWidth;
 | |
|         vscale = screenHeight / srcHeight;
 | |
|         if (uniformScale) {
 | |
|             hscale = Math.min(hscale, vscale);
 | |
|             vscale = hscale;
 | |
|         }
 | |
|         var hinvScale = 1 / hscale;
 | |
|         var vinvScale = 1 / vscale;
 | |
|         var sxmin = x0 - hinvScale * 5;
 | |
|         var symin = y0 - vinvScale * 10;
 | |
|         var sxmax = x1 + hinvScale * (6 * decimal_places + 10);
 | |
|         var symax = y1 + vinvScale * 10;
 | |
|         srcWidth = sxmax - sxmin;
 | |
|         srcHeight = symax - symin;
 | |
|         hscale = usableWidth / srcWidth;
 | |
|         vscale = screenHeight / srcHeight;
 | |
|         if (uniformScale) {
 | |
|             hscale = Math.min(hscale, vscale);
 | |
|             vscale = hscale;
 | |
|         }
 | |
|         srcLeft = sxmin;
 | |
|         srcTop = symin;
 | |
|     }
 | |
| 
 | |
| function dxy_at_t(curve, t) {
 | |
|     var dxy = {};
 | |
|     if (curve.length == 6) {
 | |
|         var a = t - 1;
 | |
|         var b = 1 - 2 * t;
 | |
|         var c = t;
 | |
|         dxy.x = a * curve[0] + b * curve[2] + c * curve[4];
 | |
|         dxy.y = a * curve[1] + b * curve[3] + c * curve[5];
 | |
|     } else if (curve.length == 7) {
 | |
|         var p20x = curve[4] - curve[0];
 | |
|         var p20y = curve[5] - curve[1];
 | |
|         var p10xw = (curve[2] - curve[0]) * curve[6];
 | |
|         var p10yw = (curve[3] - curve[1]) * curve[6];
 | |
|         var coeff0x = curve[6] * p20x - p20x;
 | |
|         var coeff0y = curve[6] * p20y - p20y;
 | |
|         var coeff1x = p20x - 2 * p10xw;
 | |
|         var coeff1y = p20y - 2 * p10yw;
 | |
|         dxy.x = t * (t * coeff0x + coeff1x) + p10xw;
 | |
|         dxy.y = t * (t * coeff0y + coeff1y) + p10yw;
 | |
|     } else if (curve.length == 8) {
 | |
|         var one_t = 1 - t;
 | |
|         var a = curve[0];
 | |
|         var b = curve[2];
 | |
|         var c = curve[4];
 | |
|         var d = curve[6];
 | |
|         dxy.x = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
 | |
|         a = curve[1];
 | |
|         b = curve[3];
 | |
|         c = curve[5];
 | |
|         d = curve[7];
 | |
|         dxy.y = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
 | |
|     }
 | |
|     return dxy;
 | |
| }
 | |
| 
 | |
|     var flt_epsilon = 1.19209290E-07;
 | |
| 
 | |
|     function approximately_zero(A) {
 | |
|         return Math.abs(A) < flt_epsilon;
 | |
|     }
 | |
| 
 | |
|     function approximately_zero_inverse(A) {
 | |
|         return Math.abs(A) > (1 / flt_epsilon);
 | |
|     }
 | |
| 
 | |
|     function quad_real_roots(A, B, C) {
 | |
|         var s = [];
 | |
|         var p = B / (2 * A);
 | |
|         var q = C / A;
 | |
|         if (approximately_zero(A) && (approximately_zero_inverse(p)
 | |
|                 || approximately_zero_inverse(q))) {
 | |
|             if (approximately_zero(B)) {
 | |
|                 if (C == 0) {
 | |
|                     s[0] = 0;
 | |
|                 }
 | |
|                 return s;
 | |
|             }
 | |
|             s[0] = -C / B;
 | |
|             return s;
 | |
|         }
 | |
|         /* normal form: x^2 + px + q = 0 */
 | |
|         var p2 = p * p;
 | |
|         if (!approximately_zero(p2 - q) && p2 < q) {
 | |
|             return s;
 | |
|         }
 | |
|         var sqrt_D = 0;
 | |
|         if (p2 > q) {
 | |
|             sqrt_D = Math.sqrt(p2 - q);
 | |
|         }
 | |
|         s[0] = sqrt_D - p;
 | |
|         var flip = -sqrt_D - p;
 | |
|         if (!approximately_zero(s[0] - flip)) {
 | |
|             s[1] = flip;
 | |
|         }
 | |
|         return s;
 | |
|     }
 | |
| 
 | |
|     function cubic_real_roots(A, B, C, D) {
 | |
|         if (approximately_zero(A)) {  // we're just a quadratic
 | |
|             return quad_real_roots(B, C, D);
 | |
|         }
 | |
|         if (approximately_zero(D)) {  // 0 is one root
 | |
|             var s = quad_real_roots(A, B, C);
 | |
|             for (var i = 0; i < s.length; ++i) {
 | |
|                 if (approximately_zero(s[i])) {
 | |
|                     return s;
 | |
|                 }
 | |
|             }
 | |
|             s.push(0);
 | |
|             return s;
 | |
|         }
 | |
|         if (approximately_zero(A + B + C + D)) {  // 1 is one root
 | |
|             var s = quad_real_roots(A, A + B, -D);
 | |
|             for (var i = 0; i < s.length; ++i) {
 | |
|                 if (approximately_zero(s[i] - 1)) {
 | |
|                     return s;
 | |
|                 }
 | |
|             }
 | |
|             s.push(1);
 | |
|             return s;
 | |
|         }
 | |
|         var a, b, c;
 | |
|         var invA = 1 / A;
 | |
|         a = B * invA;
 | |
|         b = C * invA;
 | |
|         c = D * invA;
 | |
|         var a2 = a * a;
 | |
|         var Q = (a2 - b * 3) / 9;
 | |
|         var R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
 | |
|         var R2 = R * R;
 | |
|         var Q3 = Q * Q * Q;
 | |
|         var R2MinusQ3 = R2 - Q3;
 | |
|         var adiv3 = a / 3;
 | |
|         var r;
 | |
|         var roots = [];
 | |
|         if (R2MinusQ3 < 0) {   // we have 3 real roots
 | |
|             var theta = Math.acos(R / Math.sqrt(Q3));
 | |
|             var neg2RootQ = -2 * Math.sqrt(Q);
 | |
|             r = neg2RootQ * Math.cos(theta / 3) - adiv3;
 | |
|             roots.push(r);
 | |
|             r = neg2RootQ * Math.cos((theta + 2 * Math.PI) / 3) - adiv3;
 | |
|             if (!approximately_zero(roots[0] - r)) {
 | |
|                 roots.push(r);
 | |
|             }
 | |
|             r = neg2RootQ * Math.cos((theta - 2 * Math.PI) / 3) - adiv3;
 | |
|             if (!approximately_zero(roots[0] - r) && (roots.length == 1
 | |
|                         || !approximately_zero(roots[1] - r))) {
 | |
|                 roots.push(r);
 | |
|             }
 | |
|         } else {  // we have 1 real root
 | |
|             var sqrtR2MinusQ3 = Math.sqrt(R2MinusQ3);
 | |
|             var A = Math.abs(R) + sqrtR2MinusQ3;
 | |
|             A = Math.pow(A, 1/3);
 | |
|             if (R > 0) {
 | |
|                 A = -A;
 | |
|             }
 | |
|             if (A != 0) {
 | |
|                 A += Q / A;
 | |
|             }
 | |
|             r = A - adiv3;
 | |
|             roots.push(r);
 | |
|             if (approximately_zero(R2 - Q3)) {
 | |
|                 r = -A / 2 - adiv3;
 | |
|                 if (!approximately_zero(roots[0] - r)) {
 | |
|                     roots.push(r);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         return roots;
 | |
|     }
 | |
| 
 | |
|     function approximately_zero_or_more(tValue) {
 | |
|         return tValue >= -flt_epsilon;
 | |
|     }
 | |
| 
 | |
|     function approximately_one_or_less(tValue) {
 | |
|         return tValue <= 1 + flt_epsilon;
 | |
|     }
 | |
| 
 | |
|     function approximately_less_than_zero(tValue) {
 | |
|         return tValue < flt_epsilon;
 | |
|     }
 | |
| 
 | |
|     function approximately_greater_than_one(tValue) {
 | |
|         return tValue > 1 - flt_epsilon;
 | |
|     }
 | |
| 
 | |
|     function add_valid_ts(s) {
 | |
|         var t = [];
 | |
|     nextRoot:
 | |
|         for (var index = 0; index < s.length; ++index) {
 | |
|             var tValue = s[index];
 | |
|             if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) {
 | |
|                 if (approximately_less_than_zero(tValue)) {
 | |
|                     tValue = 0;
 | |
|                 } else if (approximately_greater_than_one(tValue)) {
 | |
|                     tValue = 1;
 | |
|                 }
 | |
|                 for (var idx2 = 0; idx2 < t.length; ++idx2) {
 | |
|                     if (approximately_zero(t[idx2] - tValue)) {
 | |
|                         continue nextRoot;
 | |
|                     }
 | |
|                 }
 | |
|                 t.push(tValue);
 | |
|             }
 | |
|         }
 | |
|         return t;
 | |
|     }
 | |
| 
 | |
|     function quad_roots(A, B, C) {
 | |
|         var s = quad_real_roots(A, B, C);
 | |
|         var foundRoots = add_valid_ts(s);
 | |
|         return foundRoots;
 | |
|     }
 | |
| 
 | |
|     function cubic_roots(A, B, C, D) {
 | |
|         var s = cubic_real_roots(A, B, C, D);
 | |
|         var foundRoots = add_valid_ts(s);
 | |
|         return foundRoots;
 | |
|     }
 | |
| 
 | |
|     function ray_curve_intersect(startPt, endPt, curve) {
 | |
|         var adj = endPt[0] - startPt[0];
 | |
|         var opp = endPt[1] - startPt[1];
 | |
|         var r = [];
 | |
|         var len = (curve.length == 7 ? 6 : curve.length) / 2;
 | |
|         for (var n = 0; n < len; ++n) {
 | |
|             r[n] = (curve[n * 2 + 1] - startPt[1]) * adj - (curve[n * 2] - startPt[0]) * opp;
 | |
|         }
 | |
|         if (curve.length == 6) {
 | |
|             var A = r[2];
 | |
|             var B = r[1];
 | |
|             var C = r[0];
 | |
|             A += C - 2 * B;  // A = a - 2*b + c
 | |
|             B -= C;  // B = -(b - c)
 | |
|             return quad_roots(A, 2 * B, C);
 | |
|         }
 | |
|         if (curve.length == 7) {
 | |
|             var A = r[2];
 | |
|             var B = r[1] * curve[6];
 | |
|             var C = r[0];
 | |
|             A += C - 2 * B;  // A = a - 2*b + c
 | |
|             B -= C;  // B = -(b - c)
 | |
|             return quad_roots(A, 2 * B, C);
 | |
|         }
 | |
|         var A = r[3];       // d
 | |
|         var B = r[2] * 3;   // 3*c
 | |
|         var C = r[1] * 3;   // 3*b
 | |
|         var D = r[0];       // a
 | |
|         A -= D - C + B;     // A =   -a + 3*b - 3*c + d
 | |
|         B += 3 * D - 2 * C; // B =  3*a - 6*b + 3*c
 | |
|         C -= 3 * D;         // C = -3*a + 3*b
 | |
|         return cubic_roots(A, B, C, D);
 | |
|     }
 | |
| 
 | |
|     function x_at_t(curve, t) {
 | |
|         var one_t = 1 - t;
 | |
|         if (curve.length == 4) {
 | |
|             return one_t * curve[0] + t * curve[2];
 | |
|         }
 | |
|         var one_t2 = one_t * one_t;
 | |
|         var t2 = t * t;
 | |
|         if (curve.length == 6) {
 | |
|             return one_t2 * curve[0] + 2 * one_t * t * curve[2] + t2 * curve[4];
 | |
|         }
 | |
|         if (curve.length == 7) {
 | |
|             var numer = one_t2 * curve[0] + 2 * one_t * t * curve[2] * curve[6]
 | |
|                     + t2 * curve[4];
 | |
|             var denom = one_t2            + 2 * one_t * t            * curve[6]
 | |
|                     + t2;
 | |
|             return numer / denom;
 | |
|         }
 | |
|         var a = one_t2 * one_t;
 | |
|         var b = 3 * one_t2 * t;
 | |
|         var c = 3 * one_t * t2;
 | |
|         var d = t2 * t;
 | |
|         return a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6];
 | |
|     }
 | |
| 
 | |
|     function y_at_t(curve, t) {
 | |
|         var one_t = 1 - t;
 | |
|         if (curve.length == 4) {
 | |
|             return one_t * curve[1] + t * curve[3];
 | |
|         }
 | |
|         var one_t2 = one_t * one_t;
 | |
|         var t2 = t * t;
 | |
|         if (curve.length == 6) {
 | |
|             return one_t2 * curve[1] + 2 * one_t * t * curve[3] + t2 * curve[5];
 | |
|         }
 | |
|         if (curve.length == 7) {
 | |
|             var numer = one_t2 * curve[1] + 2 * one_t * t * curve[3] * curve[6]
 | |
|                     + t2 * curve[5];
 | |
|             var denom = one_t2            + 2 * one_t * t            * curve[6]
 | |
|                     + t2;
 | |
|             return numer / denom;
 | |
|         }
 | |
|         var a = one_t2 * one_t;
 | |
|         var b = 3 * one_t2 * t;
 | |
|         var c = 3 * one_t * t2;
 | |
|         var d = t2 * t;
 | |
|         return a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7];
 | |
|     }
 | |
| 
 | |
|     function drawPointAtT(curve) {
 | |
|         var x = x_at_t(curve, curveT);
 | |
|         var y = y_at_t(curve, curveT);
 | |
|         drawPoint(x, y, false);
 | |
|     }
 | |
| 
 | |
|     function drawLine(x1, y1, x2, y2) {
 | |
|         ctx.beginPath();
 | |
|         ctx.moveTo((x1 - srcLeft) * hscale,
 | |
|                 (y1 - srcTop) * vscale);
 | |
|         ctx.lineTo((x2 - srcLeft) * hscale,
 | |
|                 (y2 - srcTop) * vscale);
 | |
|         ctx.stroke();
 | |
|     }
 | |
| 
 | |
|     function drawPoint(px, py, xend) {
 | |
|         for (var pts = 0; pts < drawnPts.length; pts += 2) {
 | |
|             var x = drawnPts[pts];
 | |
|             var y = drawnPts[pts + 1];
 | |
|             if (px == x && py == y) {
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
|         drawnPts.push(px);
 | |
|         drawnPts.push(py);
 | |
|         var _px = (px - srcLeft) * hscale;
 | |
|         var _py = (py - srcTop) * vscale;
 | |
|         ctx.beginPath();
 | |
|         if (xend) {
 | |
|             ctx.moveTo(_px - 3, _py - 3);
 | |
|             ctx.lineTo(_px + 3, _py + 3);
 | |
|             ctx.moveTo(_px - 3, _py + 3);
 | |
|             ctx.lineTo(_px + 3, _py - 3);
 | |
|         } else {
 | |
|             ctx.arc(_px, _py, 3, 0, Math.PI * 2, true);
 | |
|             ctx.closePath();
 | |
|         }
 | |
|         ctx.stroke();
 | |
|         if (draw_point_xy) {
 | |
|             var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places);
 | |
|             ctx.font = "normal 10px Arial";
 | |
|             ctx.textAlign = "left";
 | |
|             ctx.fillStyle = "black";
 | |
|             ctx.fillText(label, _px + 5, _py);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function drawPointSolid(px, py) {
 | |
|         drawPoint(px, py, false);
 | |
|         ctx.fillStyle = "rgba(0,0,0, 0.4)";
 | |
|         ctx.fill();
 | |
|     }
 | |
| 
 | |
|     function crossPt(origin, pt1, pt2) {
 | |
|         return ((pt1[0] - origin[0]) * (pt2[1] - origin[1])
 | |
|               - (pt1[1] - origin[1]) * (pt2[0] - origin[0])) > 0 ? 0 : 1;
 | |
|     }
 | |
| 
 | |
|     // may not work well for cubics
 | |
|     function curveClosestT(curve, x, y) {
 | |
|         var closest = -1;
 | |
|         var closestDist = Infinity;
 | |
|         var l = Infinity, t = Infinity, r = -Infinity, b = -Infinity;
 | |
|         for (var i = 0; i < 16; ++i) {
 | |
|             var testX = x_at_t(curve, i / 16);
 | |
|             l = Math.min(testX, l);
 | |
|             r = Math.max(testX, r);
 | |
|             var testY = y_at_t(curve, i / 16);
 | |
|             t = Math.min(testY, t);
 | |
|             b = Math.max(testY, b);
 | |
|             var dx = testX - x;
 | |
|             var dy = testY - y;
 | |
|             var dist = dx * dx + dy * dy;
 | |
|             if (closestDist > dist) {
 | |
|                 closestDist = dist;
 | |
|                 closest = i;
 | |
|             }
 | |
|         }
 | |
|         var boundsX = r - l;
 | |
|         var boundsY = b - t;
 | |
|         var boundsDist = boundsX * boundsX + boundsY * boundsY;
 | |
|         if (closestDist > boundsDist) {
 | |
|             return -1;
 | |
|         }
 | |
|         console.log("closestDist = " + closestDist + " boundsDist = " + boundsDist
 | |
|                 + " t = " + closest / 16);
 | |
|         return closest / 16;
 | |
|     }
 | |
| 
 | |
|     var kMaxConicToQuadPOW2 = 5;
 | |
| 
 | |
|     function computeQuadPOW2(curve, tol) {
 | |
|         var a = curve[6] - 1;
 | |
|         var k = a / (4 * (2 + a));
 | |
|         var x = k * (curve[0] - 2 * curve[2] + curve[4]);
 | |
|         var y = k * (curve[1] - 2 * curve[3] + curve[5]);
 | |
| 
 | |
|         var error = Math.sqrt(x * x + y * y);
 | |
|         var pow2;
 | |
|         for (pow2 = 0; pow2 < kMaxConicToQuadPOW2; ++pow2) {
 | |
|             if (error <= tol) {
 | |
|                 break;
 | |
|             }
 | |
|             error *= 0.25;
 | |
|         }
 | |
|         return pow2;
 | |
|     }
 | |
| 
 | |
|     function subdivide_w_value(w) {
 | |
|         return Math.sqrt(0.5 + w * 0.5);
 | |
|     }
 | |
| 
 | |
|     function chop(curve, part1, part2) {
 | |
|         var w = curve[6];
 | |
|         var scale = 1 / (1 + w);
 | |
|         part1[0] = curve[0];
 | |
|         part1[1] = curve[1];
 | |
|         part1[2] = (curve[0] + curve[2] * w) * scale;
 | |
|         part1[3] = (curve[1] + curve[3] * w) * scale;
 | |
|         part1[4] = part2[0] = (curve[0] + (curve[2] * w) * 2 + curve[4]) * scale * 0.5;
 | |
|         part1[5] = part2[1] = (curve[1] + (curve[3] * w) * 2 + curve[5]) * scale * 0.5;
 | |
|         part2[2] = (curve[2] * w + curve[4]) * scale;
 | |
|         part2[3] = (curve[3] * w + curve[5]) * scale;
 | |
|         part2[4] = curve[4];
 | |
|         part2[5] = curve[5];
 | |
|         part1[6] = part2[6] = subdivide_w_value(w);
 | |
|     }
 | |
| 
 | |
|     function subdivide(curve, level, pts) {
 | |
|         if (0 == level) {
 | |
|             pts.push(curve[2]);
 | |
|             pts.push(curve[3]);
 | |
|             pts.push(curve[4]);
 | |
|             pts.push(curve[5]);
 | |
|         } else {
 | |
|             var part1 = [], part2 = [];
 | |
|             chop(curve, part1, part2);
 | |
|             --level;
 | |
|             subdivide(part1, level, pts);
 | |
|             subdivide(part2, level, pts);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function chopIntoQuadsPOW2(curve, pow2, pts) {
 | |
|         subdivide(curve, pow2, pts);
 | |
|         return 1 << pow2;
 | |
|     }
 | |
| 
 | |
|     function drawConic(curve, srcLeft, srcTop, hscale, vscale) {
 | |
|         var tol = 1 / Math.min(hscale, vscale);
 | |
|         var pow2 = computeQuadPOW2(curve, tol);
 | |
|         var pts = [];
 | |
|         chopIntoQuadsPOW2(curve, pow2, pts);
 | |
|         for (var i = 0; i < pts.length; i += 4) {
 | |
|             ctx.quadraticCurveTo(
 | |
|                 (pts[i + 0] - srcLeft) * hscale, (pts[i + 1] - srcTop) * vscale,
 | |
|                 (pts[i + 2] - srcLeft) * hscale, (pts[i + 3] - srcTop) * vscale);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function draw(test, title) {
 | |
|         ctx.font = "normal 50px Arial";
 | |
|         ctx.textAlign = "left";
 | |
|         ctx.fillStyle = "rgba(0,0,0, 0.1)";
 | |
|         ctx.fillText(title, 50, 50);
 | |
|         ctx.font = "normal 10px Arial";
 | |
|         //  ctx.lineWidth = "1.001"; "0.999";
 | |
|         var hullStarts = [];
 | |
|         var hullEnds = [];
 | |
|         var midSpokes = [];
 | |
|         var midDist = [];
 | |
|         var origin = [];
 | |
|         var shortSpokes = [];
 | |
|         var shortDist = [];
 | |
|         var sweeps = [];
 | |
|         drawnPts = [];
 | |
|         for (var curves in test) {
 | |
|             var curve = test[curves];
 | |
|             origin.push(curve[0]);
 | |
|             origin.push(curve[1]);
 | |
|             var startPt = [];
 | |
|             startPt.push(curve[2]);
 | |
|             startPt.push(curve[3]);
 | |
|             hullStarts.push(startPt);
 | |
|             var endPt = [];
 | |
|             if (curve.length == 4) {
 | |
|                 endPt.push(curve[2]);
 | |
|                 endPt.push(curve[3]);
 | |
|             } else if (curve.length == 6 || curve.length == 7) {
 | |
|                 endPt.push(curve[4]);
 | |
|                 endPt.push(curve[5]);
 | |
|             } else if (curve.length == 8) {
 | |
|                 endPt.push(curve[6]);
 | |
|                 endPt.push(curve[7]);
 | |
|             }
 | |
|             hullEnds.push(endPt);
 | |
|             var sweep = crossPt(origin, startPt, endPt);
 | |
|             sweeps.push(sweep);
 | |
|             var midPt = [];
 | |
|             midPt.push(x_at_t(curve, 0.5));
 | |
|             midPt.push(y_at_t(curve, 0.5));
 | |
|             midSpokes.push(midPt);
 | |
|             var shortPt = [];
 | |
|             shortPt.push(x_at_t(curve, 0.25));
 | |
|             shortPt.push(y_at_t(curve, 0.25));
 | |
|             shortSpokes.push(shortPt);
 | |
|             var dx = midPt[0] - origin[0];
 | |
|             var dy = midPt[1] - origin[1];
 | |
|             var dist = Math.sqrt(dx * dx + dy * dy);
 | |
|             midDist.push(dist);
 | |
|             dx = shortPt[0] - origin[0];
 | |
|             dy = shortPt[1] - origin[1];
 | |
|             dist = Math.sqrt(dx * dx + dy * dy);
 | |
|             shortDist.push(dist);
 | |
|         }
 | |
|         var intersect = [];
 | |
|         var useIntersect = false;
 | |
|         var maxWidth = Math.max(xmax - xmin, ymax - ymin);
 | |
|         for (var curves in test) {
 | |
|             var curve = test[curves];
 | |
|             if (curve.length >= 6 && curve.length <= 8) {
 | |
|                 var opp = curves == 0 || curves == 1 ? 0 : 1;
 | |
|                 var sects = ray_curve_intersect(origin, hullEnds[opp], curve);
 | |
|                 intersect.push(sects);
 | |
|                 if (sects.length > 1) {
 | |
|                     var intersection = sects[0];
 | |
|                     if (intersection == 0) {
 | |
|                         intersection = sects[1];
 | |
|                     }
 | |
|                     var ix = x_at_t(curve, intersection) - origin[0];
 | |
|                     var iy = y_at_t(curve, intersection) - origin[1];
 | |
|                     var ex = hullEnds[opp][0] - origin[0];
 | |
|                     var ey = hullEnds[opp][1] - origin[1];
 | |
|                     if (ix * ex >= 0 && iy * ey >= 0) {
 | |
|                         var iDist = Math.sqrt(ix * ix + iy * iy);
 | |
|                         var eDist = Math.sqrt(ex * ex + ey * ey);
 | |
|                         var delta = Math.abs(iDist - eDist) / maxWidth;
 | |
|                         if (delta > (curve.length != 8 ? 1e-5 : 1e-4)) {
 | |
|                             useIntersect ^= true;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         var midLeft = curves != 0 ? crossPt(origin, midSpokes[0], midSpokes[1]) : 0;
 | |
|         var firstInside;
 | |
|         if (useIntersect) {
 | |
|             var sect1 = intersect[0].length > 1;
 | |
|             var sIndex = sect1 ? 0 : 1;
 | |
|             var sects = intersect[sIndex];
 | |
|             var intersection = sects[0];
 | |
|             if (intersection == 0) {
 | |
|                 intersection = sects[1];
 | |
|             }
 | |
|             var curve = test[sIndex];
 | |
|             var ix = x_at_t(curve, intersection) - origin[0];
 | |
|             var iy = y_at_t(curve, intersection) - origin[1];
 | |
|             var opp = sect1 ? 1 : 0;
 | |
|             var ex = hullEnds[opp][0] - origin[0];
 | |
|             var ey = hullEnds[opp][1] - origin[1];
 | |
|             var iDist = ix * ix + iy * iy;
 | |
|             var eDist = ex * ex + ey * ey;
 | |
|             firstInside = (iDist > eDist) ^ (sIndex == 0) ^ sweeps[0];
 | |
| //            console.log("iDist=" + iDist + " eDist=" + eDist + " sIndex=" + sIndex
 | |
|  //                   + " sweeps[0]=" + sweeps[0]);
 | |
|         } else {
 | |
|  //           console.log("midLeft=" + midLeft);
 | |
|             firstInside = midLeft != 0;
 | |
|         }
 | |
|         var shorter = midDist[1] < midDist[0];
 | |
|         var shortLeft = shorter ? crossPt(origin, shortSpokes[0], midSpokes[1])
 | |
|                 : crossPt(origin, midSpokes[0], shortSpokes[1]);
 | |
|         var startCross = crossPt(origin, hullStarts[0], hullStarts[1]);
 | |
|         var disallowShort = midLeft == startCross && midLeft == sweeps[0]
 | |
|                     && midLeft == sweeps[1];
 | |
| 
 | |
|   //      console.log("midLeft=" + midLeft + " startCross=" + startCross);
 | |
|         var intersectIndex = 0;
 | |
|         for (var curves in test) {
 | |
|             var curve = test[draw_id != 2 ? curves : test.length - curves - 1];
 | |
|             if (curve.length != 4 && curve.length != 6 && curve.length != 7 && curve.length != 8) {
 | |
|                 continue;
 | |
|             }
 | |
|             ctx.lineWidth = 1;
 | |
|             if (draw_tangents != 0) {
 | |
|                 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
 | |
|                     ctx.strokeStyle = "rgba(255,0,0, 0.3)";
 | |
|                 } else {
 | |
|                     ctx.strokeStyle = "rgba(0,0,255, 0.3)";
 | |
|                 }
 | |
|                 drawLine(curve[0], curve[1], curve[2], curve[3]);
 | |
|                 if (draw_tangents != 2) {
 | |
|                     if (curve.length > 4) drawLine(curve[2], curve[3], curve[4], curve[5]);
 | |
|                     if (curve.length == 8) drawLine(curve[4], curve[5], curve[6], curve[7]);
 | |
|                 }
 | |
|                 if (draw_tangents != 1) {
 | |
|                     if (curve.length == 6 || curve.length == 7) {
 | |
|                         drawLine(curve[0], curve[1], curve[4], curve[5]);
 | |
|                     }
 | |
|                     if (curve.length == 8) drawLine(curve[0], curve[1], curve[6], curve[7]);
 | |
|                 }
 | |
|             }
 | |
|             ctx.beginPath();
 | |
|             ctx.moveTo((curve[0] - srcLeft) * hscale, (curve[1] - srcTop) * vscale);
 | |
|             if (curve.length == 4) {
 | |
|                 ctx.lineTo((curve[2] - srcLeft) * hscale, (curve[3] - srcTop) * vscale);
 | |
|             } else if (curve.length == 6) {
 | |
|                 ctx.quadraticCurveTo(
 | |
|                     (curve[2] - srcLeft) * hscale, (curve[3] - srcTop) * vscale,
 | |
|                     (curve[4] - srcLeft) * hscale, (curve[5] - srcTop) * vscale);
 | |
|             } else if (curve.length == 7) {
 | |
|                 drawConic(curve, srcLeft, srcTop, hscale, vscale);
 | |
|             } else {
 | |
|                 ctx.bezierCurveTo(
 | |
|                     (curve[2] - srcLeft) * hscale, (curve[3] - srcTop) * vscale,
 | |
|                     (curve[4] - srcLeft) * hscale, (curve[5] - srcTop) * vscale,
 | |
|                     (curve[6] - srcLeft) * hscale, (curve[7] - srcTop) * vscale);
 | |
|             }
 | |
|             if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
 | |
|                 ctx.strokeStyle = "rgba(255,0,0, 1)";
 | |
|             } else {
 | |
|                 ctx.strokeStyle = "rgba(0,0,255, 1)";
 | |
|             }
 | |
|             ctx.stroke();
 | |
|             if (draw_endpoints > 0) {
 | |
|                 drawPoint(curve[0], curve[1], false);
 | |
|                 if (draw_endpoints > 1 || curve.length == 4) {
 | |
|                     drawPoint(curve[2], curve[3], curve.length == 4 && draw_endpoints == 3);
 | |
|                 }
 | |
|                 if (curve.length == 6 || curve.length == 7 ||
 | |
|                         (draw_endpoints > 1 && curve.length == 8)) {
 | |
|                     drawPoint(curve[4], curve[5], (curve.length == 6 || curve.length == 7) && draw_endpoints == 3);
 | |
|                 }
 | |
|                 if (curve.length == 8) {
 | |
|                     drawPoint(curve[6], curve[7], curve.length == 8 && draw_endpoints == 3);
 | |
|                 }
 | |
|             }
 | |
|             if (draw_midpoint != 0) {
 | |
|                 if ((curves == 0) == (midLeft == 0)) {
 | |
|                     ctx.strokeStyle = "rgba(0,180,127, 0.6)";
 | |
|                 } else {
 | |
|                     ctx.strokeStyle = "rgba(127,0,127, 0.6)";
 | |
|                 }
 | |
|                 var midX = x_at_t(curve, 0.5);
 | |
|                 var midY = y_at_t(curve, 0.5);
 | |
|                 drawPointSolid(midX, midY);
 | |
|                 if (draw_midpoint > 1) {
 | |
|                     drawLine(curve[0], curve[1], midX, midY);
 | |
|                 }
 | |
|             }
 | |
|             if (draw_quarterpoint != 0) {
 | |
|                 if ((curves == 0) == (shortLeft == 0)) {
 | |
|                     ctx.strokeStyle = "rgba(0,191,63, 0.6)";
 | |
|                 } else {
 | |
|                     ctx.strokeStyle = "rgba(63,0,191, 0.6)";
 | |
|                 }
 | |
|                 var midT = (curves == 0) == shorter ? 0.25 : 0.5;
 | |
|                 var midX = x_at_t(curve, midT);
 | |
|                 var midY = y_at_t(curve, midT);
 | |
|                 drawPointSolid(midX, midY);
 | |
|                 if (draw_quarterpoint > 1) {
 | |
|                     drawLine(curve[0], curve[1], midX, midY);
 | |
|                 }
 | |
|             }
 | |
|             if (draw_sortpoint != 0) {
 | |
|                 if ((curves == 0) == ((disallowShort == -1 ? midLeft : shortLeft) == 0)) {
 | |
|                     ctx.strokeStyle = "rgba(0,155,37, 0.6)";
 | |
|                 } else {
 | |
|                     ctx.strokeStyle = "rgba(37,0,155, 0.6)";
 | |
|                 }
 | |
|                 var midT = (curves == 0) == shorter && disallowShort != curves ? 0.25 : 0.5;
 | |
|                 console.log("curves=" + curves + " disallowShort=" + disallowShort
 | |
|                         + " midLeft=" + midLeft + " shortLeft=" + shortLeft
 | |
|                         + " shorter=" + shorter + " midT=" + midT);
 | |
|                 var midX = x_at_t(curve, midT);
 | |
|                 var midY = y_at_t(curve, midT);
 | |
|                 drawPointSolid(midX, midY);
 | |
|                 if (draw_sortpoint > 1) {
 | |
|                     drawLine(curve[0], curve[1], midX, midY);
 | |
|                 }
 | |
|             }
 | |
|             if (draw_ray_intersect != 0) {
 | |
|                 ctx.strokeStyle = "rgba(75,45,199, 0.6)";
 | |
|                 if (curve.length >= 6 && curve.length <= 8) {
 | |
|                     var intersections = intersect[intersectIndex];
 | |
|                     for (var i in intersections) {
 | |
|                         var intersection = intersections[i];
 | |
|                         var x = x_at_t(curve, intersection);
 | |
|                         var y = y_at_t(curve, intersection);
 | |
|                         drawPointSolid(x, y);
 | |
|                         if (draw_ray_intersect > 1) {
 | |
|                             drawLine(curve[0], curve[1], x, y);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 ++intersectIndex;
 | |
|             }
 | |
|             if (draw_order) {
 | |
|                 var px = x_at_t(curve, 0.75);
 | |
|                 var py = y_at_t(curve, 0.75);
 | |
|                 var _px = (px - srcLeft) * hscale;
 | |
|                 var _py = (py - srcTop) * vscale;
 | |
|                 ctx.beginPath();
 | |
|                 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
 | |
|                 ctx.closePath();
 | |
|                 ctx.fillStyle = "white";
 | |
|                 ctx.fill();
 | |
|                 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
 | |
|                     ctx.strokeStyle = "rgba(255,0,0, 1)";
 | |
|                     ctx.fillStyle = "rgba(255,0,0, 1)";
 | |
|                 } else {
 | |
|                     ctx.strokeStyle = "rgba(0,0,255, 1)";
 | |
|                     ctx.fillStyle = "rgba(0,0,255, 1)";
 | |
|                 }
 | |
|                 ctx.stroke();
 | |
|                 ctx.font = "normal 16px Arial";
 | |
|                 ctx.textAlign = "center";
 | |
|                 ctx.fillText(parseInt(curves) + 1, _px, _py + 5);
 | |
|             }
 | |
|             if (draw_closest_t) {
 | |
|                 var t = curveClosestT(curve, mouseX, mouseY);
 | |
|                 if (t >= 0) {
 | |
|                     var x = x_at_t(curve, t);
 | |
|                     var y = y_at_t(curve, t);
 | |
|                     drawPointSolid(x, y);
 | |
|                 }
 | |
|             }
 | |
|             if (!approximately_zero(hscale - hinitScale)) {
 | |
|                 ctx.font = "normal 20px Arial";
 | |
|                 ctx.fillStyle = "rgba(0,0,0, 0.3)";
 | |
|                 ctx.textAlign = "right";
 | |
|                 var scaleTextOffset = hscale != vscale ? -25 : -5;
 | |
|                 ctx.fillText(hscale.toFixed(decimal_places) + 'x',
 | |
|                         screenWidth - 10, screenHeight - scaleTextOffset);
 | |
|                 if (hscale != vscale) {
 | |
|                     ctx.fillText(vscale.toFixed(decimal_places) + 'y',
 | |
|                             screenWidth - 10, screenHeight - 5);
 | |
|                 }
 | |
|             }
 | |
|             if (draw_t) {
 | |
|                 drawPointAtT(curve);
 | |
|             }
 | |
|             if (draw_id != 0) {
 | |
|                 var id = -1;
 | |
|                 for (var i = 0; i < ids.length; i += 2) {
 | |
|                     if (ids[i + 1] == curve) {
 | |
|                         id = ids[i];
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|                 if (id >= 0) {
 | |
|                     var px = x_at_t(curve, 0.5);
 | |
|                     var py = y_at_t(curve, 0.5);
 | |
|                     var _px = (px - srcLeft) * hscale;
 | |
|                     var _py = (py - srcTop) * vscale;
 | |
|                     ctx.beginPath();
 | |
|                     ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
 | |
|                     ctx.closePath();
 | |
|                     ctx.fillStyle = "white";
 | |
|                     ctx.fill();
 | |
|                     ctx.strokeStyle = "rgba(255,0,0, 1)";
 | |
|                     ctx.fillStyle = "rgba(255,0,0, 1)";
 | |
|                     ctx.stroke();
 | |
|                     ctx.font = "normal 16px Arial";
 | |
|                     ctx.textAlign = "center";
 | |
|                     ctx.fillText(id, _px, _py + 5);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         if (draw_t) {
 | |
|             drawCurveTControl();
 | |
|         }
 | |
|         if (draw_w) {
 | |
|             drawCurveWControl();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function drawCurveTControl() {
 | |
|         ctx.lineWidth = 2;
 | |
|         ctx.strokeStyle = "rgba(0,0,0, 0.3)";
 | |
|         ctx.beginPath();
 | |
|         ctx.rect(screenWidth - 80, 40, 28, screenHeight - 80);
 | |
|         ctx.stroke();
 | |
|         var ty = 40 + curveT * (screenHeight - 80);
 | |
|         ctx.beginPath();
 | |
|         ctx.moveTo(screenWidth - 80, ty);
 | |
|         ctx.lineTo(screenWidth - 85, ty - 5);
 | |
|         ctx.lineTo(screenWidth - 85, ty + 5);
 | |
|         ctx.lineTo(screenWidth - 80, ty);
 | |
|         ctx.fillStyle = "rgba(0,0,0, 0.6)";
 | |
|         ctx.fill();
 | |
|         var num = curveT.toFixed(decimal_places);
 | |
|         ctx.font = "normal 10px Arial";
 | |
|         ctx.textAlign = "left";
 | |
|         ctx.fillText(num, screenWidth - 78, ty);
 | |
|     }
 | |
| 
 | |
|     function drawCurveWControl() {
 | |
|         var w = -1;
 | |
|         var choice = 0;
 | |
|         for (var curves in tests[testIndex]) {
 | |
|             var curve = tests[testIndex][curves];
 | |
|             if (curve.length != 7) {
 | |
|                 continue;
 | |
|             }
 | |
|             if (choice == curveW) {
 | |
|                 w = curve[6];
 | |
|                 break;
 | |
|             }
 | |
|             ++choice;
 | |
|         }
 | |
|         if (w < 0) {
 | |
|             return;
 | |
|         }
 | |
|         ctx.lineWidth = 2;
 | |
|         ctx.strokeStyle = "rgba(0,0,0, 0.3)";
 | |
|         ctx.beginPath();
 | |
|         ctx.rect(screenWidth - 40, 40, 28, screenHeight - 80);
 | |
|         ctx.stroke();
 | |
|         var ty = 40 + w * (screenHeight - 80);
 | |
|         ctx.beginPath();
 | |
|         ctx.moveTo(screenWidth - 40, ty);
 | |
|         ctx.lineTo(screenWidth - 45, ty - 5);
 | |
|         ctx.lineTo(screenWidth - 45, ty + 5);
 | |
|         ctx.lineTo(screenWidth - 40, ty);
 | |
|         ctx.fillStyle = "rgba(0,0,0, 0.6)";
 | |
|         ctx.fill();
 | |
|         var num = w.toFixed(decimal_places);
 | |
|         ctx.font = "normal 10px Arial";
 | |
|         ctx.textAlign = "left";
 | |
|         ctx.fillText(num, screenWidth - 38, ty);
 | |
|     }
 | |
| 
 | |
|     function ptInTControl() {
 | |
|         var e = window.event;
 | |
|         var tgt = e.target || e.srcElement;
 | |
|         var left = tgt.offsetLeft;
 | |
|         var top = tgt.offsetTop;
 | |
|         var x = (e.clientX - left);
 | |
|         var y = (e.clientY - top);
 | |
|         if (x < screenWidth - 80 || x > screenWidth - 50) {
 | |
|             return false;
 | |
|         }
 | |
|         if (y < 40 || y > screenHeight - 80) {
 | |
|             return false;
 | |
|         }
 | |
|         curveT = (y - 40) / (screenHeight - 120);
 | |
|         if (curveT < 0 || curveT > 1) {
 | |
|             throw "stop execution";
 | |
|         }
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     function ptInWControl() {
 | |
|         var e = window.event;
 | |
|         var tgt = e.target || e.srcElement;
 | |
|         var left = tgt.offsetLeft;
 | |
|         var top = tgt.offsetTop;
 | |
|         var x = (e.clientX - left);
 | |
|         var y = (e.clientY - top);
 | |
|         if (x < screenWidth - 40 || x > screenWidth - 10) {
 | |
|             return false;
 | |
|         }
 | |
|         if (y < 40 || y > screenHeight - 80) {
 | |
|             return false;
 | |
|         }
 | |
|         var w = (y - 40) / (screenHeight - 120);
 | |
|         if (w < 0 || w > 1) {
 | |
|             throw "stop execution";
 | |
|         }
 | |
|         var choice = 0;
 | |
|         for (var curves in tests[testIndex]) {
 | |
|             var curve = tests[testIndex][curves];
 | |
|             if (curve.length != 7) {
 | |
|                 continue;
 | |
|             }
 | |
|             if (choice == curveW) {
 | |
|                 curve[6] = w;
 | |
|                 break;
 | |
|             }
 | |
|             ++choice;
 | |
|         }
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     function drawTop() {
 | |
|         init(tests[testIndex]);
 | |
|         redraw();
 | |
|     }
 | |
| 
 | |
|     function redraw() {
 | |
|         if (focus_on_selection > 0) {
 | |
|             var focusXmin = focusYmin = Infinity;
 | |
|             var focusXmax = focusYmax = -Infinity;
 | |
|             var choice = 0;
 | |
|             for (var curves in tests[testIndex]) {
 | |
|                 if (++choice != focus_on_selection) {
 | |
|                     continue;
 | |
|                 }
 | |
|                 var curve = tests[testIndex][curves];
 | |
|                 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
 | |
|                 for (var idx = 0; idx < last; idx += 2) {
 | |
|                     focusXmin = Math.min(focusXmin, curve[idx]);
 | |
|                     focusXmax = Math.max(focusXmax, curve[idx]);
 | |
|                     focusYmin = Math.min(focusYmin, curve[idx + 1]);
 | |
|                     focusYmax = Math.max(focusYmax, curve[idx + 1]);
 | |
|                 }
 | |
|             }
 | |
|             focusXmin -= Math.min(1, Math.max(focusXmax - focusXmin, focusYmax - focusYmin));
 | |
|             if (focusXmin < focusXmax && focusYmin < focusYmax) {
 | |
|                 setScale(focusXmin, focusXmax, focusYmin, focusYmax);
 | |
|             }
 | |
|         }
 | |
|         ctx.beginPath();
 | |
|         ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
 | |
|         ctx.fillStyle = "white";
 | |
|         ctx.fill();
 | |
|         draw(tests[testIndex], testTitles[testIndex]);
 | |
|     }
 | |
| 
 | |
|     function doKeyPress(evt) {
 | |
|         var char = String.fromCharCode(evt.charCode);
 | |
|         var focusWasOn = false;
 | |
|         switch (char) {
 | |
|             case '0':
 | |
|             case '1':
 | |
|             case '2':
 | |
|             case '3':
 | |
|             case '4':
 | |
|             case '5':
 | |
|             case '6':
 | |
|             case '7':
 | |
|             case '8':
 | |
|             case '9':
 | |
|                 decimal_places = char - '0';
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case '-':
 | |
|                 focusWasOn = focus_on_selection;
 | |
|                 if (focusWasOn) {
 | |
|                     focus_on_selection = false;
 | |
|                     hscale /= 1.2;
 | |
|                     vscale /= 1.2;
 | |
|                 } else {
 | |
|                     hscale /= 2;
 | |
|                     vscale /= 2;
 | |
|                 }
 | |
|                 calcLeftTop();
 | |
|                 redraw();
 | |
|                 focus_on_selection = focusWasOn;
 | |
|                 break;
 | |
|             case '=':
 | |
|             case '+':
 | |
|                 focusWasOn = focus_on_selection;
 | |
|                 if (focusWasOn) {
 | |
|                     focus_on_selection = false;
 | |
|                     hscale *= 1.2;
 | |
|                     vscale *= 1.2;
 | |
|                 } else {
 | |
|                     hscale *= 2;
 | |
|                     vscale *= 2;
 | |
|                 }
 | |
|                 calcLeftTop();
 | |
|                 redraw();
 | |
|                 focus_on_selection = focusWasOn;
 | |
|                 break;
 | |
|             case 'b':
 | |
|                 draw_cubic_red ^= true;
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'c':
 | |
|                 drawTop();
 | |
|                 break;
 | |
|             case 'd':
 | |
|                 var test = tests[testIndex];
 | |
|                 var testClone = [];
 | |
|                 for (var curves in test) {
 | |
|                     var c = test[curves];
 | |
|                     var cClone = [];
 | |
|                     for (var index = 0; index < c.length; ++index) {
 | |
|                         cClone.push(c[index]);
 | |
|                     }
 | |
|                     testClone.push(cClone);
 | |
|                 }
 | |
|                 tests.push(testClone);
 | |
|                 testTitles.push(testTitles[testIndex] + " copy");
 | |
|                 testIndex = tests.length - 1;
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'e':
 | |
|                 draw_endpoints = (draw_endpoints + 1) % 4;
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'f':
 | |
|                 draw_derivative ^= true;
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'g':
 | |
|                 hscale *= 1.2;
 | |
|                 calcLeftTop();
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'G':
 | |
|                 hscale /= 1.2;
 | |
|                 calcLeftTop();
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'h':
 | |
|                 vscale *= 1.2;
 | |
|                 calcLeftTop();
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'H':
 | |
|                 vscale /= 1.2;
 | |
|                 calcLeftTop();
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'i':
 | |
|                 draw_ray_intersect = (draw_ray_intersect + 1) % 3;
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'l':
 | |
|                 var test = tests[testIndex];
 | |
|                 console.log("<div id=\"" + testTitles[testIndex] + "\" >");
 | |
|                 for (var curves in test) {
 | |
|                     var c = test[curves];
 | |
|                     var s = "{{";
 | |
|                     for (var i = 0; i < c.length; i += 2) {
 | |
|                         s += "{";
 | |
|                         s += c[i] + "," + c[i + 1];
 | |
|                         s += "}";
 | |
|                         if (i + 2 < c.length) {
 | |
|                             s += ", ";
 | |
|                         }
 | |
|                     }
 | |
|                     console.log(s + "}},");
 | |
|                 }
 | |
|                 console.log("</div>");
 | |
|                 break;
 | |
|             case 'm':
 | |
|                 draw_midpoint = (draw_midpoint + 1) % 3;
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'N':
 | |
|                 testIndex += 9;
 | |
|             case 'n':
 | |
|                 testIndex = (testIndex + 1) % tests.length;
 | |
|                 drawTop();
 | |
|                 break;
 | |
|             case 'o':
 | |
|                 draw_order ^= true;
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'P':
 | |
|                 testIndex -= 9;
 | |
|             case 'p':
 | |
|                 if (--testIndex < 0)
 | |
|                     testIndex = tests.length - 1;
 | |
|                 drawTop();
 | |
|                 break;
 | |
|             case 'q':
 | |
|                 draw_quarterpoint = (draw_quarterpoint + 1) % 3;
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'r':
 | |
|                 for (var i = 0; i < testDivs.length; ++i) {
 | |
|                     var title = testDivs[i].id.toString();
 | |
|                     if (title == testTitles[testIndex]) {
 | |
|                         var str = testDivs[i].firstChild.data;
 | |
|                         parse(str, title);
 | |
|                         var original = tests.pop();
 | |
|                         testTitles.pop();
 | |
|                         tests[testIndex] = original;
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 's':
 | |
|                 draw_sortpoint = (draw_sortpoint + 1) % 3;
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 't':
 | |
|                 draw_t ^= true;
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'u':
 | |
|                 draw_closest_t ^= true;
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'v':
 | |
|                 draw_tangents = (draw_tangents + 1) % 4;
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'w':
 | |
|                 ++curveW;
 | |
|                 var choice = 0;
 | |
|                 draw_w = false;
 | |
|                 for (var curves in tests[testIndex]) {
 | |
|                     var curve = tests[testIndex][curves];
 | |
|                     if (curve.length != 7) {
 | |
|                         continue;
 | |
|                     }
 | |
|                     if (choice == curveW) {
 | |
|                         draw_w = true;
 | |
|                         break;
 | |
|                     }
 | |
|                     ++choice;
 | |
|                 }
 | |
|                 if (!draw_w) {
 | |
|                     curveW = -1;
 | |
|                 }
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'x':
 | |
|                 draw_point_xy ^= true;
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case 'y':
 | |
|                 draw_mouse_xy ^= true;
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case '\\':
 | |
|                 retina_scale ^= true;
 | |
|                 drawTop();
 | |
|                 break;
 | |
|             case '`':
 | |
|                 ++focus_on_selection;
 | |
|                 if (focus_on_selection >= tests[testIndex].length) {
 | |
|                     focus_on_selection = 0;
 | |
|                 }
 | |
|                 setScale(xmin, xmax, ymin, ymax);
 | |
|                 redraw();
 | |
|                 break;
 | |
|             case '.':
 | |
|                 draw_id = (draw_id + 1) % 3;
 | |
|                 redraw();
 | |
|                 break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function doKeyDown(evt) {
 | |
|         var char = evt.keyCode;
 | |
|         var preventDefault = false;
 | |
|         switch (char) {
 | |
|             case 37: // left arrow
 | |
|                 if (evt.shiftKey) {
 | |
|                     testIndex -= 9;
 | |
|                 }
 | |
|                 if (--testIndex < 0)
 | |
|                     testIndex = tests.length - 1;
 | |
|                 if (evt.ctrlKey) {
 | |
|                     redraw();
 | |
|                 } else {
 | |
|                     drawTop();
 | |
|                 }
 | |
|                 preventDefault = true;
 | |
|                 break;
 | |
|             case 39: // right arrow
 | |
|                 if (evt.shiftKey) {
 | |
|                     testIndex += 9;
 | |
|                 }
 | |
|                 if (++testIndex >= tests.length)
 | |
|                     testIndex = 0;
 | |
|                 if (evt.ctrlKey) {
 | |
|                     redraw();
 | |
|                 } else {
 | |
|                     drawTop();
 | |
|                 }
 | |
|                 preventDefault = true;
 | |
|                 break;
 | |
|         }
 | |
|         if (preventDefault) {
 | |
|             evt.preventDefault();
 | |
|             return false;
 | |
|         }
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     function calcXY() {
 | |
|         var e = window.event;
 | |
|         var tgt = e.target || e.srcElement;
 | |
|         var left = tgt.offsetLeft;
 | |
|         var top = tgt.offsetTop;
 | |
|         mouseX = (e.clientX - left) / hscale + srcLeft;
 | |
|         mouseY = (e.clientY - top) / vscale + srcTop;
 | |
|     }
 | |
| 
 | |
|     function calcLeftTop() {
 | |
|         srcLeft = mouseX - screenWidth / 2 / hscale;
 | |
|         srcTop = mouseY - screenHeight / 2 / vscale;
 | |
|     }
 | |
| 
 | |
|     function handleMouseClick() {
 | |
|         if ((!draw_t || !ptInTControl()) && (!draw_w || !ptInWControl())) {
 | |
|             calcXY();
 | |
|         } else {
 | |
|             redraw();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function initDown() {
 | |
|         var test = tests[testIndex];
 | |
|         var bestDistance = 1000000;
 | |
|         activePt = -1;
 | |
|         for (var curves in test) {
 | |
|             var testCurve = test[curves];
 | |
|             if (testCurve.length != 4 && (testCurve.length < 6 || testCurve.length > 8)) {
 | |
|                 continue;
 | |
|             }
 | |
|             var testMax = testCurve.length == 7 ? 6 : testCurve.length;
 | |
|             for (var i = 0; i < testMax; i += 2) {
 | |
|                 var testX = testCurve[i];
 | |
|                 var testY = testCurve[i + 1];
 | |
|                 var dx = testX - mouseX;
 | |
|                 var dy = testY - mouseY;
 | |
|                 var dist = dx * dx + dy * dy;
 | |
|                 if (dist > bestDistance) {
 | |
|                     continue;
 | |
|                 }
 | |
|                 activeCurve = testCurve;
 | |
|                 activePt = i;
 | |
|                 bestDistance = dist;
 | |
|             }
 | |
|         }
 | |
|         if (activePt >= 0) {
 | |
|             lastX = mouseX;
 | |
|             lastY = mouseY;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function handleMouseOver() {
 | |
|         calcXY();
 | |
|         if (draw_mouse_xy) {
 | |
|             var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places);
 | |
|             ctx.beginPath();
 | |
|             ctx.rect(300, 100, num.length * 6, 10);
 | |
|             ctx.fillStyle = "white";
 | |
|             ctx.fill();
 | |
|             ctx.font = "normal 10px Arial";
 | |
|             ctx.fillStyle = "black";
 | |
|             ctx.textAlign = "left";
 | |
|             ctx.fillText(num, 300, 108);
 | |
|         }
 | |
|         if (!mouseDown) {
 | |
|             activePt = -1;
 | |
|             return;
 | |
|         }
 | |
|         if (activePt < 0) {
 | |
|             initDown();
 | |
|             return;
 | |
|         }
 | |
|         var deltaX = mouseX - lastX;
 | |
|         var deltaY = mouseY - lastY;
 | |
|         lastX = mouseX;
 | |
|         lastY = mouseY;
 | |
|         if (activePt == 0) {
 | |
|             var test = tests[testIndex];
 | |
|             for (var curves in test) {
 | |
|                 var testCurve = test[curves];
 | |
|                 testCurve[0] += deltaX;
 | |
|                 testCurve[1] += deltaY;
 | |
|             }
 | |
|         } else {
 | |
|             activeCurve[activePt] += deltaX;
 | |
|             activeCurve[activePt + 1] += deltaY;
 | |
|         }
 | |
|         redraw();
 | |
|     }
 | |
| 
 | |
|     function start() {
 | |
|         for (var i = 0; i < testDivs.length; ++i) {
 | |
|             var title = testDivs[i].id.toString();
 | |
|             var str = testDivs[i].firstChild.data;
 | |
|             parse(str, title);
 | |
|         }
 | |
|         drawTop();
 | |
|         window.addEventListener('keypress', doKeyPress, true);
 | |
|         window.addEventListener('keydown', doKeyDown, true);
 | |
|         window.onresize = function () {
 | |
|             drawTop();
 | |
|         }
 | |
|     }
 | |
| 
 | |
| </script>
 | |
| </head>
 | |
| 
 | |
| <body onLoad="start();">
 | |
| 
 | |
| <canvas id="canvas" width="750" height="500"
 | |
|     onmousedown="mouseDown = true"
 | |
|     onmouseup="mouseDown = false"
 | |
|     onmousemove="handleMouseOver()"
 | |
|     onclick="handleMouseClick()"
 | |
|     ></canvas >
 | |
| </body>
 | |
| </html> |