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