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