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> |