1 <!DOCTYPE html> 2 3 <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> 4 <head> 5 <meta charset="utf-8" /> 6 <title></title> 7 <div style="height:0"> 8 9 <div id="sect1"> 10 {{{6, -3}, {0, 1}}} id=3 11 {{{1.6714313f, -1.08141601f}, {2.24979973f, -1.14467525f}, {2.27122664f, -0.514151096f}, {0, 1}}} id=5 12 {{{0.001119050197303295135, 0.9992539882659912109}, {4.001119136810302734, 6.999254226684570312}}}, 13 </div> 14 15 </div> 16 17 <script type="text/javascript"> 18 19 var testDivs = [ 20 sect1, 21 ]; 22 23 var decimal_places = 3; 24 25 var tests = []; 26 var testTitles = []; 27 var testIndex = 0; 28 var ctx; 29 30 var subscale = 1; 31 var xmin, xmax, ymin, ymax; 32 var scale; 33 var initScale; 34 var mouseX, mouseY; 35 var mouseDown = false; 36 var srcLeft, srcTop; 37 var screenWidth, screenHeight; 38 var drawnPts; 39 var curveT = 0; 40 var curveW = -1; 41 42 var lastX, lastY; 43 var activeCurve = []; 44 var activePt; 45 var ids = []; 46 47 var focus_on_selection = 0; 48 var draw_t = false; 49 var draw_w = false; 50 var draw_closest_t = false; 51 var draw_cubic_red = false; 52 var draw_derivative = false; 53 var draw_endpoints = 2; 54 var draw_id = 0; 55 var draw_midpoint = 0; 56 var draw_mouse_xy = false; 57 var draw_order = false; 58 var draw_point_xy = false; 59 var draw_ray_intersect = false; 60 var draw_quarterpoint = 0; 61 var draw_tangents = 1; 62 var draw_sortpoint = 0; 63 var retina_scale = !!window.devicePixelRatio; 64 65 function parse(test, title) { 66 var curveStrs = test.split("{{"); 67 var pattern = /-?\d+\.*\d*e?-?\d*/g; 68 var curves = []; 69 for (var c in curveStrs) { 70 var curveStr = curveStrs[c]; 71 var idPart = curveStr.split("id="); 72 var id = -1; 73 if (idPart.length == 2) { 74 id = parseInt(idPart[1]); 75 curveStr = idPart[0]; 76 } 77 var points = curveStr.match(pattern); 78 var pts = []; 79 for (var wd in points) { 80 var num = parseFloat(points[wd]); 81 if (isNaN(num)) continue; 82 pts.push(num); 83 } 84 if (pts.length > 2) { 85 curves.push(pts); 86 } 87 if (id >= 0) { 88 ids.push(id); 89 ids.push(pts); 90 } 91 } 92 if (curves.length >= 1) { 93 tests.push(curves); 94 testTitles.push(title); 95 } 96 } 97 98 function init(test) { 99 var canvas = document.getElementById('canvas'); 100 if (!canvas.getContext) return; 101 ctx = canvas.getContext('2d'); 102 var resScale = retina_scale && window.devicePixelRatio ? window.devicePixelRatio : 1; 103 var unscaledWidth = window.innerWidth - 20; 104 var unscaledHeight = window.innerHeight - 20; 105 screenWidth = unscaledWidth; 106 screenHeight = unscaledHeight; 107 canvas.width = unscaledWidth * resScale; 108 canvas.height = unscaledHeight * resScale; 109 canvas.style.width = unscaledWidth + 'px'; 110 canvas.style.height = unscaledHeight + 'px'; 111 if (resScale != 1) { 112 ctx.scale(resScale, resScale); 113 } 114 xmin = Infinity; 115 xmax = -Infinity; 116 ymin = Infinity; 117 ymax = -Infinity; 118 for (var curves in test) { 119 var curve = test[curves]; 120 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0); 121 for (var idx = 0; idx < last; idx += 2) { 122 xmin = Math.min(xmin, curve[idx]); 123 xmax = Math.max(xmax, curve[idx]); 124 ymin = Math.min(ymin, curve[idx + 1]); 125 ymax = Math.max(ymax, curve[idx + 1]); 126 } 127 } 128 xmin -= Math.min(1, Math.max(xmax - xmin, ymax - ymin)); 129 var testW = xmax - xmin; 130 var testH = ymax - ymin; 131 subscale = 1; 132 while (testW * subscale < 0.1 && testH * subscale < 0.1) { 133 subscale *= 10; 134 } 135 while (testW * subscale > 10 && testH * subscale > 10) { 136 subscale /= 10; 137 } 138 setScale(xmin, xmax, ymin, ymax); 139 mouseX = (screenWidth / 2) / scale + srcLeft; 140 mouseY = (screenHeight / 2) / scale + srcTop; 141 initScale = scale; 142 } 143 144 function setScale(x0, x1, y0, y1) { 145 var srcWidth = x1 - x0; 146 var srcHeight = y1 - y0; 147 var usableWidth = screenWidth; 148 var xDigits = Math.ceil(Math.log(Math.abs(xmax)) / Math.log(10)); 149 var yDigits = Math.ceil(Math.log(Math.abs(ymax)) / Math.log(10)); 150 usableWidth -= (xDigits + yDigits) * 10; 151 usableWidth -= decimal_places * 10; 152 var hscale = usableWidth / srcWidth; 153 var vscale = screenHeight / srcHeight; 154 scale = Math.min(hscale, vscale); 155 var invScale = 1 / scale; 156 var sxmin = x0 - invScale * 5; 157 var symin = y0 - invScale * 10; 158 var sxmax = x1 + invScale * (6 * decimal_places + 10); 159 var symax = y1 + invScale * 10; 160 srcWidth = sxmax - sxmin; 161 srcHeight = symax - symin; 162 hscale = usableWidth / srcWidth; 163 vscale = screenHeight / srcHeight; 164 scale = Math.min(hscale, vscale); 165 srcLeft = sxmin; 166 srcTop = symin; 167 } 168 169 function dxy_at_t(curve, t) { 170 var dxy = {}; 171 if (curve.length == 6) { 172 var a = t - 1; 173 var b = 1 - 2 * t; 174 var c = t; 175 dxy.x = a * curve[0] + b * curve[2] + c * curve[4]; 176 dxy.y = a * curve[1] + b * curve[3] + c * curve[5]; 177 } else if (curve.length == 7) { 178 var p20x = curve[4] - curve[0]; 179 var p20y = curve[5] - curve[1]; 180 var p10xw = (curve[2] - curve[0]) * curve[6]; 181 var p10yw = (curve[3] - curve[1]) * curve[6]; 182 var coeff0x = curve[6] * p20x - p20x; 183 var coeff0y = curve[6] * p20y - p20y; 184 var coeff1x = p20x - 2 * p10xw; 185 var coeff1y = p20y - 2 * p10yw; 186 dxy.x = t * (t * coeff0x + coeff1x) + p10xw; 187 dxy.y = t * (t * coeff0y + coeff1y) + p10yw; 188 } else if (curve.length == 8) { 189 var one_t = 1 - t; 190 var a = curve[0]; 191 var b = curve[2]; 192 var c = curve[4]; 193 var d = curve[6]; 194 dxy.x = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t); 195 a = curve[1]; 196 b = curve[3]; 197 c = curve[5]; 198 d = curve[7]; 199 dxy.y = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t); 200 } 201 return dxy; 202 } 203 204 var flt_epsilon = 1.19209290E-07; 205 206 function approximately_zero(A) { 207 return Math.abs(A) < flt_epsilon; 208 } 209 210 function approximately_zero_inverse(A) { 211 return Math.abs(A) > (1 / flt_epsilon); 212 } 213 214 function quad_real_roots(A, B, C) { 215 var s = []; 216 var p = B / (2 * A); 217 var q = C / A; 218 if (approximately_zero(A) && (approximately_zero_inverse(p) 219 || approximately_zero_inverse(q))) { 220 if (approximately_zero(B)) { 221 if (C == 0) { 222 s[0] = 0; 223 } 224 return s; 225 } 226 s[0] = -C / B; 227 return s; 228 } 229 /* normal form: x^2 + px + q = 0 */ 230 var p2 = p * p; 231 if (!approximately_zero(p2 - q) && p2 < q) { 232 return s; 233 } 234 var sqrt_D = 0; 235 if (p2 > q) { 236 sqrt_D = Math.sqrt(p2 - q); 237 } 238 s[0] = sqrt_D - p; 239 var flip = -sqrt_D - p; 240 if (!approximately_zero(s[0] - flip)) { 241 s[1] = flip; 242 } 243 return s; 244 } 245 246 function cubic_real_roots(A, B, C, D) { 247 if (approximately_zero(A)) { // we're just a quadratic 248 return quad_real_roots(B, C, D); 249 } 250 if (approximately_zero(D)) { // 0 is one root 251 var s = quad_real_roots(A, B, C); 252 for (var i = 0; i < s.length; ++i) { 253 if (approximately_zero(s[i])) { 254 return s; 255 } 256 } 257 s.push(0); 258 return s; 259 } 260 if (approximately_zero(A + B + C + D)) { // 1 is one root 261 var s = quad_real_roots(A, A + B, -D); 262 for (var i = 0; i < s.length; ++i) { 263 if (approximately_zero(s[i] - 1)) { 264 return s; 265 } 266 } 267 s.push(1); 268 return s; 269 } 270 var a, b, c; 271 var invA = 1 / A; 272 a = B * invA; 273 b = C * invA; 274 c = D * invA; 275 var a2 = a * a; 276 var Q = (a2 - b * 3) / 9; 277 var R = (2 * a2 * a - 9 * a * b + 27 * c) / 54; 278 var R2 = R * R; 279 var Q3 = Q * Q * Q; 280 var R2MinusQ3 = R2 - Q3; 281 var adiv3 = a / 3; 282 var r; 283 var roots = []; 284 if (R2MinusQ3 < 0) { // we have 3 real roots 285 var theta = Math.acos(R / Math.sqrt(Q3)); 286 var neg2RootQ = -2 * Math.sqrt(Q); 287 r = neg2RootQ * Math.cos(theta / 3) - adiv3; 288 roots.push(r); 289 r = neg2RootQ * Math.cos((theta + 2 * Math.PI) / 3) - adiv3; 290 if (!approximately_zero(roots[0] - r)) { 291 roots.push(r); 292 } 293 r = neg2RootQ * Math.cos((theta - 2 * Math.PI) / 3) - adiv3; 294 if (!approximately_zero(roots[0] - r) && (roots.length == 1 295 || !approximately_zero(roots[1] - r))) { 296 roots.push(r); 297 } 298 } else { // we have 1 real root 299 var sqrtR2MinusQ3 = Math.sqrt(R2MinusQ3); 300 var A = Math.abs(R) + sqrtR2MinusQ3; 301 A = Math.pow(A, 1/3); 302 if (R > 0) { 303 A = -A; 304 } 305 if (A != 0) { 306 A += Q / A; 307 } 308 r = A - adiv3; 309 roots.push(r); 310 if (approximately_zero(R2 - Q3)) { 311 r = -A / 2 - adiv3; 312 if (!approximately_zero(roots[0] - r)) { 313 roots.push(r); 314 } 315 } 316 } 317 return roots; 318 } 319 320 function approximately_zero_or_more(tValue) { 321 return tValue >= -flt_epsilon; 322 } 323 324 function approximately_one_or_less(tValue) { 325 return tValue <= 1 + flt_epsilon; 326 } 327 328 function approximately_less_than_zero(tValue) { 329 return tValue < flt_epsilon; 330 } 331 332 function approximately_greater_than_one(tValue) { 333 return tValue > 1 - flt_epsilon; 334 } 335 336 function add_valid_ts(s) { 337 var t = []; 338 nextRoot: 339 for (var index = 0; index < s.length; ++index) { 340 var tValue = s[index]; 341 if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) { 342 if (approximately_less_than_zero(tValue)) { 343 tValue = 0; 344 } else if (approximately_greater_than_one(tValue)) { 345 tValue = 1; 346 } 347 for (var idx2 = 0; idx2 < t.length; ++idx2) { 348 if (approximately_zero(t[idx2] - tValue)) { 349 continue nextRoot; 350 } 351 } 352 t.push(tValue); 353 } 354 } 355 return t; 356 } 357 358 function quad_roots(A, B, C) { 359 var s = quad_real_roots(A, B, C); 360 var foundRoots = add_valid_ts(s); 361 return foundRoots; 362 } 363 364 function cubic_roots(A, B, C, D) { 365 var s = cubic_real_roots(A, B, C, D); 366 var foundRoots = add_valid_ts(s); 367 return foundRoots; 368 } 369 370 function ray_curve_intersect(startPt, endPt, curve) { 371 var adj = endPt[0] - startPt[0]; 372 var opp = endPt[1] - startPt[1]; 373 var r = []; 374 var len = (curve.length == 7 ? 6 : curve.length) / 2; 375 for (var n = 0; n < len; ++n) { 376 r[n] = (curve[n * 2 + 1] - startPt[1]) * adj - (curve[n * 2] - startPt[0]) * opp; 377 } 378 if (curve.length == 6) { 379 var A = r[2]; 380 var B = r[1]; 381 var C = r[0]; 382 A += C - 2 * B; // A = a - 2*b + c 383 B -= C; // B = -(b - c) 384 return quad_roots(A, 2 * B, C); 385 } 386 if (curve.length == 7) { 387 var A = r[2]; 388 var B = r[1] * curve[6]; 389 var C = r[0]; 390 A += C - 2 * B; // A = a - 2*b + c 391 B -= C; // B = -(b - c) 392 return quad_roots(A, 2 * B, C); 393 } 394 var A = r[3]; // d 395 var B = r[2] * 3; // 3*c 396 var C = r[1] * 3; // 3*b 397 var D = r[0]; // a 398 A -= D - C + B; // A = -a + 3*b - 3*c + d 399 B += 3 * D - 2 * C; // B = 3*a - 6*b + 3*c 400 C -= 3 * D; // C = -3*a + 3*b 401 return cubic_roots(A, B, C, D); 402 } 403 404 function x_at_t(curve, t) { 405 var one_t = 1 - t; 406 if (curve.length == 4) { 407 return one_t * curve[0] + t * curve[2]; 408 } 409 var one_t2 = one_t * one_t; 410 var t2 = t * t; 411 if (curve.length == 6) { 412 return one_t2 * curve[0] + 2 * one_t * t * curve[2] + t2 * curve[4]; 413 } 414 if (curve.length == 7) { 415 var numer = one_t2 * curve[0] + 2 * one_t * t * curve[2] * curve[6] 416 + t2 * curve[4]; 417 var denom = one_t2 + 2 * one_t * t * curve[6] 418 + t2; 419 return numer / denom; 420 } 421 var a = one_t2 * one_t; 422 var b = 3 * one_t2 * t; 423 var c = 3 * one_t * t2; 424 var d = t2 * t; 425 return a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6]; 426 } 427 428 function y_at_t(curve, t) { 429 var one_t = 1 - t; 430 if (curve.length == 4) { 431 return one_t * curve[1] + t * curve[3]; 432 } 433 var one_t2 = one_t * one_t; 434 var t2 = t * t; 435 if (curve.length == 6) { 436 return one_t2 * curve[1] + 2 * one_t * t * curve[3] + t2 * curve[5]; 437 } 438 if (curve.length == 7) { 439 var numer = one_t2 * curve[1] + 2 * one_t * t * curve[3] * curve[6] 440 + t2 * curve[5]; 441 var denom = one_t2 + 2 * one_t * t * curve[6] 442 + t2; 443 return numer / denom; 444 } 445 var a = one_t2 * one_t; 446 var b = 3 * one_t2 * t; 447 var c = 3 * one_t * t2; 448 var d = t2 * t; 449 return a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7]; 450 } 451 452 function drawPointAtT(curve) { 453 var x = x_at_t(curve, curveT); 454 var y = y_at_t(curve, curveT); 455 drawPoint(x, y); 456 } 457 458 function drawLine(x1, y1, x2, y2) { 459 ctx.beginPath(); 460 ctx.moveTo((x1 - srcLeft) * scale, 461 (y1 - srcTop) * scale); 462 ctx.lineTo((x2 - srcLeft) * scale, 463 (y2 - srcTop) * scale); 464 ctx.stroke(); 465 } 466 467 function drawPoint(px, py) { 468 for (var pts = 0; pts < drawnPts.length; pts += 2) { 469 var x = drawnPts[pts]; 470 var y = drawnPts[pts + 1]; 471 if (px == x && py == y) { 472 return; 473 } 474 } 475 drawnPts.push(px); 476 drawnPts.push(py); 477 var _px = (px - srcLeft) * scale; 478 var _py = (py - srcTop) * scale; 479 ctx.beginPath(); 480 ctx.arc(_px, _py, 3, 0, Math.PI * 2, true); 481 ctx.closePath(); 482 ctx.stroke(); 483 if (draw_point_xy) { 484 var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places); 485 ctx.font = "normal 10px Arial"; 486 ctx.textAlign = "left"; 487 ctx.fillStyle = "black"; 488 ctx.fillText(label, _px + 5, _py); 489 } 490 } 491 492 function drawPointSolid(px, py) { 493 drawPoint(px, py); 494 ctx.fillStyle = "rgba(0,0,0, 0.4)"; 495 ctx.fill(); 496 } 497 498 function crossPt(origin, pt1, pt2) { 499 return ((pt1[0] - origin[0]) * (pt2[1] - origin[1]) 500 - (pt1[1] - origin[1]) * (pt2[0] - origin[0])) > 0 ? 0 : 1; 501 } 502 503 // may not work well for cubics 504 function curveClosestT(curve, x, y) { 505 var closest = -1; 506 var closestDist = Infinity; 507 var l = Infinity, t = Infinity, r = -Infinity, b = -Infinity; 508 for (var i = 0; i < 16; ++i) { 509 var testX = x_at_t(curve, i / 16); 510 l = Math.min(testX, l); 511 r = Math.max(testX, r); 512 var testY = y_at_t(curve, i / 16); 513 t = Math.min(testY, t); 514 b = Math.max(testY, b); 515 var dx = testX - x; 516 var dy = testY - y; 517 var dist = dx * dx + dy * dy; 518 if (closestDist > dist) { 519 closestDist = dist; 520 closest = i; 521 } 522 } 523 var boundsX = r - l; 524 var boundsY = b - t; 525 var boundsDist = boundsX * boundsX + boundsY * boundsY; 526 if (closestDist > boundsDist) { 527 return -1; 528 } 529 console.log("closestDist = " + closestDist + " boundsDist = " + boundsDist 530 + " t = " + closest / 16); 531 return closest / 16; 532 } 533 534 var kMaxConicToQuadPOW2 = 5; 535 536 function computeQuadPOW2(curve, tol) { 537 var a = curve[6] - 1; 538 var k = a / (4 * (2 + a)); 539 var x = k * (curve[0] - 2 * curve[2] + curve[4]); 540 var y = k * (curve[1] - 2 * curve[3] + curve[5]); 541 542 var error = Math.sqrt(x * x + y * y); 543 var pow2; 544 for (pow2 = 0; pow2 < kMaxConicToQuadPOW2; ++pow2) { 545 if (error <= tol) { 546 break; 547 } 548 error *= 0.25; 549 } 550 return pow2; 551 } 552 553 function subdivide_w_value(w) { 554 return Math.sqrt(0.5 + w * 0.5); 555 } 556 557 function chop(curve, part1, part2) { 558 var w = curve[6]; 559 var scale = 1 / (1 + w); 560 part1[0] = curve[0]; 561 part1[1] = curve[1]; 562 part1[2] = (curve[0] + curve[2] * w) * scale; 563 part1[3] = (curve[1] + curve[3] * w) * scale; 564 part1[4] = part2[0] = (curve[0] + (curve[2] * w) * 2 + curve[4]) * scale * 0.5; 565 part1[5] = part2[1] = (curve[1] + (curve[3] * w) * 2 + curve[5]) * scale * 0.5; 566 part2[2] = (curve[2] * w + curve[4]) * scale; 567 part2[3] = (curve[3] * w + curve[5]) * scale; 568 part2[4] = curve[4]; 569 part2[5] = curve[5]; 570 part1[6] = part2[6] = subdivide_w_value(w); 571 } 572 573 function subdivide(curve, level, pts) { 574 if (0 == level) { 575 pts.push(curve[2]); 576 pts.push(curve[3]); 577 pts.push(curve[4]); 578 pts.push(curve[5]); 579 } else { 580 var part1 = [], part2 = []; 581 chop(curve, part1, part2); 582 --level; 583 subdivide(part1, level, pts); 584 subdivide(part2, level, pts); 585 } 586 } 587 588 function chopIntoQuadsPOW2(curve, pow2, pts) { 589 subdivide(curve, pow2, pts); 590 return 1 << pow2; 591 } 592 593 function drawConic(curve, srcLeft, srcTop, scale) { 594 var tol = 1 / scale; 595 var pow2 = computeQuadPOW2(curve, tol); 596 var pts = []; 597 chopIntoQuadsPOW2(curve, pow2, pts); 598 for (var i = 0; i < pts.length; i += 4) { 599 ctx.quadraticCurveTo( 600 (pts[i + 0] - srcLeft) * scale, (pts[i + 1] - srcTop) * scale, 601 (pts[i + 2] - srcLeft) * scale, (pts[i + 3] - srcTop) * scale); 602 } 603 } 604 605 function draw(test, title) { 606 ctx.font = "normal 50px Arial"; 607 ctx.textAlign = "left"; 608 ctx.fillStyle = "rgba(0,0,0, 0.1)"; 609 ctx.fillText(title, 50, 50); 610 ctx.font = "normal 10px Arial"; 611 // ctx.lineWidth = "1.001"; "0.999"; 612 var hullStarts = []; 613 var hullEnds = []; 614 var midSpokes = []; 615 var midDist = []; 616 var origin = []; 617 var shortSpokes = []; 618 var shortDist = []; 619 var sweeps = []; 620 drawnPts = []; 621 for (var curves in test) { 622 var curve = test[curves]; 623 origin.push(curve[0]); 624 origin.push(curve[1]); 625 var startPt = []; 626 startPt.push(curve[2]); 627 startPt.push(curve[3]); 628 hullStarts.push(startPt); 629 var endPt = []; 630 if (curve.length == 4) { 631 endPt.push(curve[2]); 632 endPt.push(curve[3]); 633 } else if (curve.length == 6 || curve.length == 7) { 634 endPt.push(curve[4]); 635 endPt.push(curve[5]); 636 } else if (curve.length == 8) { 637 endPt.push(curve[6]); 638 endPt.push(curve[7]); 639 } 640 hullEnds.push(endPt); 641 var sweep = crossPt(origin, startPt, endPt); 642 sweeps.push(sweep); 643 var midPt = []; 644 midPt.push(x_at_t(curve, 0.5)); 645 midPt.push(y_at_t(curve, 0.5)); 646 midSpokes.push(midPt); 647 var shortPt = []; 648 shortPt.push(x_at_t(curve, 0.25)); 649 shortPt.push(y_at_t(curve, 0.25)); 650 shortSpokes.push(shortPt); 651 var dx = midPt[0] - origin[0]; 652 var dy = midPt[1] - origin[1]; 653 var dist = Math.sqrt(dx * dx + dy * dy); 654 midDist.push(dist); 655 dx = shortPt[0] - origin[0]; 656 dy = shortPt[1] - origin[1]; 657 dist = Math.sqrt(dx * dx + dy * dy); 658 shortDist.push(dist); 659 } 660 var intersect = []; 661 var useIntersect = false; 662 var maxWidth = Math.max(xmax - xmin, ymax - ymin); 663 for (var curves in test) { 664 var curve = test[curves]; 665 if (curve.length >= 6 && curve.length <= 8) { 666 var opp = curves == 0 || curves == 1 ? 0 : 1; 667 var sects = ray_curve_intersect(origin, hullEnds[opp], curve); 668 intersect.push(sects); 669 if (sects.length > 1) { 670 var intersection = sects[0]; 671 if (intersection == 0) { 672 intersection = sects[1]; 673 } 674 var ix = x_at_t(curve, intersection) - origin[0]; 675 var iy = y_at_t(curve, intersection) - origin[1]; 676 var ex = hullEnds[opp][0] - origin[0]; 677 var ey = hullEnds[opp][1] - origin[1]; 678 if (ix * ex >= 0 && iy * ey >= 0) { 679 var iDist = Math.sqrt(ix * ix + iy * iy); 680 var eDist = Math.sqrt(ex * ex + ey * ey); 681 var delta = Math.abs(iDist - eDist) / maxWidth; 682 if (delta > (curve.length != 8 ? 1e-5 : 1e-4)) { 683 useIntersect ^= true; 684 } 685 } 686 } 687 } 688 } 689 var midLeft = curves != 0 ? crossPt(origin, midSpokes[0], midSpokes[1]) : 0; 690 var firstInside; 691 if (useIntersect) { 692 var sect1 = intersect[0].length > 1; 693 var sIndex = sect1 ? 0 : 1; 694 var sects = intersect[sIndex]; 695 var intersection = sects[0]; 696 if (intersection == 0) { 697 intersection = sects[1]; 698 } 699 var curve = test[sIndex]; 700 var ix = x_at_t(curve, intersection) - origin[0]; 701 var iy = y_at_t(curve, intersection) - origin[1]; 702 var opp = sect1 ? 1 : 0; 703 var ex = hullEnds[opp][0] - origin[0]; 704 var ey = hullEnds[opp][1] - origin[1]; 705 var iDist = ix * ix + iy * iy; 706 var eDist = ex * ex + ey * ey; 707 firstInside = (iDist > eDist) ^ (sIndex == 0) ^ sweeps[0]; 708 // console.log("iDist=" + iDist + " eDist=" + eDist + " sIndex=" + sIndex 709 // + " sweeps[0]=" + sweeps[0]); 710 } else { 711 // console.log("midLeft=" + midLeft); 712 firstInside = midLeft != 0; 713 } 714 var shorter = midDist[1] < midDist[0]; 715 var shortLeft = shorter ? crossPt(origin, shortSpokes[0], midSpokes[1]) 716 : crossPt(origin, midSpokes[0], shortSpokes[1]); 717 var startCross = crossPt(origin, hullStarts[0], hullStarts[1]); 718 var disallowShort = midLeft == startCross && midLeft == sweeps[0] 719 && midLeft == sweeps[1]; 720 721 // console.log("midLeft=" + midLeft + " startCross=" + startCross); 722 var intersectIndex = 0; 723 for (var curves in test) { 724 var curve = test[draw_id != 2 ? curves : test.length - curves - 1]; 725 if (curve.length != 4 && curve.length != 6 && curve.length != 7 && curve.length != 8) { 726 continue; 727 } 728 ctx.lineWidth = 1; 729 if (draw_tangents != 0) { 730 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) { 731 ctx.strokeStyle = "rgba(255,0,0, 0.3)"; 732 } else { 733 ctx.strokeStyle = "rgba(0,0,255, 0.3)"; 734 } 735 drawLine(curve[0], curve[1], curve[2], curve[3]); 736 if (draw_tangents != 2) { 737 if (curve.length > 4) drawLine(curve[2], curve[3], curve[4], curve[5]); 738 if (curve.length == 8) drawLine(curve[4], curve[5], curve[6], curve[7]); 739 } 740 if (draw_tangents != 1) { 741 if (curve.length == 6 || curve.length == 7) { 742 drawLine(curve[0], curve[1], curve[4], curve[5]); 743 } 744 if (curve.length == 8) drawLine(curve[0], curve[1], curve[6], curve[7]); 745 } 746 } 747 ctx.beginPath(); 748 ctx.moveTo((curve[0] - srcLeft) * scale, (curve[1] - srcTop) * scale); 749 if (curve.length == 4) { 750 ctx.lineTo((curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale); 751 } else if (curve.length == 6) { 752 ctx.quadraticCurveTo( 753 (curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale, 754 (curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale); 755 } else if (curve.length == 7) { 756 drawConic(curve, srcLeft, srcTop, scale); 757 } else { 758 ctx.bezierCurveTo( 759 (curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale, 760 (curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale, 761 (curve[6] - srcLeft) * scale, (curve[7] - srcTop) * scale); 762 } 763 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) { 764 ctx.strokeStyle = "rgba(255,0,0, 1)"; 765 } else { 766 ctx.strokeStyle = "rgba(0,0,255, 1)"; 767 } 768 ctx.stroke(); 769 if (draw_endpoints > 0) { 770 drawPoint(curve[0], curve[1]); 771 if (draw_endpoints > 1 || curve.length == 4) { 772 drawPoint(curve[2], curve[3]); 773 } 774 if (curve.length == 6 || curve.length == 7 || 775 (draw_endpoints > 1 && curve.length == 8)) { 776 drawPoint(curve[4], curve[5]); 777 } 778 if (curve.length == 8) drawPoint(curve[6], curve[7]); 779 } 780 if (draw_midpoint != 0) { 781 if ((curves == 0) == (midLeft == 0)) { 782 ctx.strokeStyle = "rgba(0,180,127, 0.6)"; 783 } else { 784 ctx.strokeStyle = "rgba(127,0,127, 0.6)"; 785 } 786 var midX = x_at_t(curve, 0.5); 787 var midY = y_at_t(curve, 0.5); 788 drawPointSolid(midX, midY); 789 if (draw_midpoint > 1) { 790 drawLine(curve[0], curve[1], midX, midY); 791 } 792 } 793 if (draw_quarterpoint != 0) { 794 if ((curves == 0) == (shortLeft == 0)) { 795 ctx.strokeStyle = "rgba(0,191,63, 0.6)"; 796 } else { 797 ctx.strokeStyle = "rgba(63,0,191, 0.6)"; 798 } 799 var midT = (curves == 0) == shorter ? 0.25 : 0.5; 800 var midX = x_at_t(curve, midT); 801 var midY = y_at_t(curve, midT); 802 drawPointSolid(midX, midY); 803 if (draw_quarterpoint > 1) { 804 drawLine(curve[0], curve[1], midX, midY); 805 } 806 } 807 if (draw_sortpoint != 0) { 808 if ((curves == 0) == ((disallowShort == -1 ? midLeft : shortLeft) == 0)) { 809 ctx.strokeStyle = "rgba(0,155,37, 0.6)"; 810 } else { 811 ctx.strokeStyle = "rgba(37,0,155, 0.6)"; 812 } 813 var midT = (curves == 0) == shorter && disallowShort != curves ? 0.25 : 0.5; 814 console.log("curves=" + curves + " disallowShort=" + disallowShort 815 + " midLeft=" + midLeft + " shortLeft=" + shortLeft 816 + " shorter=" + shorter + " midT=" + midT); 817 var midX = x_at_t(curve, midT); 818 var midY = y_at_t(curve, midT); 819 drawPointSolid(midX, midY); 820 if (draw_sortpoint > 1) { 821 drawLine(curve[0], curve[1], midX, midY); 822 } 823 } 824 if (draw_ray_intersect != 0) { 825 ctx.strokeStyle = "rgba(75,45,199, 0.6)"; 826 if (curve.length >= 6 && curve.length <= 8) { 827 var intersections = intersect[intersectIndex]; 828 for (var i in intersections) { 829 var intersection = intersections[i]; 830 var x = x_at_t(curve, intersection); 831 var y = y_at_t(curve, intersection); 832 drawPointSolid(x, y); 833 if (draw_ray_intersect > 1) { 834 drawLine(curve[0], curve[1], x, y); 835 } 836 } 837 } 838 ++intersectIndex; 839 } 840 if (draw_order) { 841 var px = x_at_t(curve, 0.75); 842 var py = y_at_t(curve, 0.75); 843 var _px = (px - srcLeft) * scale; 844 var _py = (py - srcTop) * scale; 845 ctx.beginPath(); 846 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true); 847 ctx.closePath(); 848 ctx.fillStyle = "white"; 849 ctx.fill(); 850 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) { 851 ctx.strokeStyle = "rgba(255,0,0, 1)"; 852 ctx.fillStyle = "rgba(255,0,0, 1)"; 853 } else { 854 ctx.strokeStyle = "rgba(0,0,255, 1)"; 855 ctx.fillStyle = "rgba(0,0,255, 1)"; 856 } 857 ctx.stroke(); 858 ctx.font = "normal 16px Arial"; 859 ctx.textAlign = "center"; 860 ctx.fillText(parseInt(curves) + 1, _px, _py + 5); 861 } 862 if (draw_closest_t) { 863 var t = curveClosestT(curve, mouseX, mouseY); 864 if (t >= 0) { 865 var x = x_at_t(curve, t); 866 var y = y_at_t(curve, t); 867 drawPointSolid(x, y); 868 } 869 } 870 if (!approximately_zero(scale - initScale)) { 871 ctx.font = "normal 20px Arial"; 872 ctx.fillStyle = "rgba(0,0,0, 0.3)"; 873 ctx.textAlign = "right"; 874 ctx.fillText(scale.toFixed(decimal_places) + 'x', 875 screenWidth - 10, screenHeight - 5); 876 } 877 if (draw_t) { 878 drawPointAtT(curve); 879 } 880 if (draw_id != 0) { 881 var id = -1; 882 for (var i = 0; i < ids.length; i += 2) { 883 if (ids[i + 1] == curve) { 884 id = ids[i]; 885 break; 886 } 887 } 888 if (id >= 0) { 889 var px = x_at_t(curve, 0.5); 890 var py = y_at_t(curve, 0.5); 891 var _px = (px - srcLeft) * scale; 892 var _py = (py - srcTop) * scale; 893 ctx.beginPath(); 894 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true); 895 ctx.closePath(); 896 ctx.fillStyle = "white"; 897 ctx.fill(); 898 ctx.strokeStyle = "rgba(255,0,0, 1)"; 899 ctx.fillStyle = "rgba(255,0,0, 1)"; 900 ctx.stroke(); 901 ctx.font = "normal 16px Arial"; 902 ctx.textAlign = "center"; 903 ctx.fillText(id, _px, _py + 5); 904 } 905 } 906 } 907 if (draw_t) { 908 drawCurveTControl(); 909 } 910 if (draw_w) { 911 drawCurveWControl(); 912 } 913 } 914 915 function drawCurveTControl() { 916 ctx.lineWidth = 2; 917 ctx.strokeStyle = "rgba(0,0,0, 0.3)"; 918 ctx.beginPath(); 919 ctx.rect(screenWidth - 80, 40, 28, screenHeight - 80); 920 ctx.stroke(); 921 var ty = 40 + curveT * (screenHeight - 80); 922 ctx.beginPath(); 923 ctx.moveTo(screenWidth - 80, ty); 924 ctx.lineTo(screenWidth - 85, ty - 5); 925 ctx.lineTo(screenWidth - 85, ty + 5); 926 ctx.lineTo(screenWidth - 80, ty); 927 ctx.fillStyle = "rgba(0,0,0, 0.6)"; 928 ctx.fill(); 929 var num = curveT.toFixed(decimal_places); 930 ctx.font = "normal 10px Arial"; 931 ctx.textAlign = "left"; 932 ctx.fillText(num, screenWidth - 78, ty); 933 } 934 935 function drawCurveWControl() { 936 var w = -1; 937 var choice = 0; 938 for (var curves in tests[testIndex]) { 939 var curve = tests[testIndex][curves]; 940 if (curve.length != 7) { 941 continue; 942 } 943 if (choice == curveW) { 944 w = curve[6]; 945 break; 946 } 947 ++choice; 948 } 949 if (w < 0) { 950 return; 951 } 952 ctx.lineWidth = 2; 953 ctx.strokeStyle = "rgba(0,0,0, 0.3)"; 954 ctx.beginPath(); 955 ctx.rect(screenWidth - 40, 40, 28, screenHeight - 80); 956 ctx.stroke(); 957 var ty = 40 + w * (screenHeight - 80); 958 ctx.beginPath(); 959 ctx.moveTo(screenWidth - 40, ty); 960 ctx.lineTo(screenWidth - 45, ty - 5); 961 ctx.lineTo(screenWidth - 45, ty + 5); 962 ctx.lineTo(screenWidth - 40, ty); 963 ctx.fillStyle = "rgba(0,0,0, 0.6)"; 964 ctx.fill(); 965 var num = w.toFixed(decimal_places); 966 ctx.font = "normal 10px Arial"; 967 ctx.textAlign = "left"; 968 ctx.fillText(num, screenWidth - 38, ty); 969 } 970 971 function ptInTControl() { 972 var e = window.event; 973 var tgt = e.target || e.srcElement; 974 var left = tgt.offsetLeft; 975 var top = tgt.offsetTop; 976 var x = (e.clientX - left); 977 var y = (e.clientY - top); 978 if (x < screenWidth - 80 || x > screenWidth - 50) { 979 return false; 980 } 981 if (y < 40 || y > screenHeight - 80) { 982 return false; 983 } 984 curveT = (y - 40) / (screenHeight - 120); 985 if (curveT < 0 || curveT > 1) { 986 throw "stop execution"; 987 } 988 return true; 989 } 990 991 function ptInWControl() { 992 var e = window.event; 993 var tgt = e.target || e.srcElement; 994 var left = tgt.offsetLeft; 995 var top = tgt.offsetTop; 996 var x = (e.clientX - left); 997 var y = (e.clientY - top); 998 if (x < screenWidth - 40 || x > screenWidth - 10) { 999 return false; 1000 } 1001 if (y < 40 || y > screenHeight - 80) { 1002 return false; 1003 } 1004 var w = (y - 40) / (screenHeight - 120); 1005 if (w < 0 || w > 1) { 1006 throw "stop execution"; 1007 } 1008 var choice = 0; 1009 for (var curves in tests[testIndex]) { 1010 var curve = tests[testIndex][curves]; 1011 if (curve.length != 7) { 1012 continue; 1013 } 1014 if (choice == curveW) { 1015 curve[6] = w; 1016 break; 1017 } 1018 ++choice; 1019 } 1020 return true; 1021 } 1022 1023 function drawTop() { 1024 init(tests[testIndex]); 1025 redraw(); 1026 } 1027 1028 function redraw() { 1029 if (focus_on_selection > 0) { 1030 var focusXmin = focusYmin = Infinity; 1031 var focusXmax = focusYmax = -Infinity; 1032 var choice = 0; 1033 for (var curves in tests[testIndex]) { 1034 if (++choice != focus_on_selection) { 1035 continue; 1036 } 1037 var curve = tests[testIndex][curves]; 1038 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0); 1039 for (var idx = 0; idx < last; idx += 2) { 1040 focusXmin = Math.min(focusXmin, curve[idx]); 1041 focusXmax = Math.max(focusXmax, curve[idx]); 1042 focusYmin = Math.min(focusYmin, curve[idx + 1]); 1043 focusYmax = Math.max(focusYmax, curve[idx + 1]); 1044 } 1045 } 1046 focusXmin -= Math.min(1, Math.max(focusXmax - focusXmin, focusYmax - focusYmin)); 1047 if (focusXmin < focusXmax && focusYmin < focusYmax) { 1048 setScale(focusXmin, focusXmax, focusYmin, focusYmax); 1049 } 1050 } 1051 ctx.beginPath(); 1052 ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height); 1053 ctx.fillStyle = "white"; 1054 ctx.fill(); 1055 draw(tests[testIndex], testTitles[testIndex]); 1056 } 1057 1058 function doKeyPress(evt) { 1059 var char = String.fromCharCode(evt.charCode); 1060 var focusWasOn = false; 1061 switch (char) { 1062 case '0': 1063 case '1': 1064 case '2': 1065 case '3': 1066 case '4': 1067 case '5': 1068 case '6': 1069 case '7': 1070 case '8': 1071 case '9': 1072 decimal_places = char - '0'; 1073 redraw(); 1074 break; 1075 case '-': 1076 focusWasOn = focus_on_selection; 1077 if (focusWasOn) { 1078 focus_on_selection = false; 1079 scale /= 1.2; 1080 } else { 1081 scale /= 2; 1082 } 1083 calcLeftTop(); 1084 redraw(); 1085 focus_on_selection = focusWasOn; 1086 break; 1087 case '=': 1088 case '+': 1089 focusWasOn = focus_on_selection; 1090 if (focusWasOn) { 1091 focus_on_selection = false; 1092 scale *= 1.2; 1093 } else { 1094 scale *= 2; 1095 } 1096 calcLeftTop(); 1097 redraw(); 1098 focus_on_selection = focusWasOn; 1099 break; 1100 case 'b': 1101 draw_cubic_red ^= true; 1102 redraw(); 1103 break; 1104 case 'c': 1105 drawTop(); 1106 break; 1107 case 'd': 1108 var test = tests[testIndex]; 1109 var testClone = []; 1110 for (var curves in test) { 1111 var c = test[curves]; 1112 var cClone = []; 1113 for (var index = 0; index < c.length; ++index) { 1114 cClone.push(c[index]); 1115 } 1116 testClone.push(cClone); 1117 } 1118 tests.push(testClone); 1119 testTitles.push(testTitles[testIndex] + " copy"); 1120 testIndex = tests.length - 1; 1121 redraw(); 1122 break; 1123 case 'e': 1124 draw_endpoints = (draw_endpoints + 1) % 3; 1125 redraw(); 1126 break; 1127 case 'f': 1128 draw_derivative ^= true; 1129 redraw(); 1130 break; 1131 case 'i': 1132 draw_ray_intersect = (draw_ray_intersect + 1) % 3; 1133 redraw(); 1134 break; 1135 case 'l': 1136 var test = tests[testIndex]; 1137 console.log("<div id=\"" + testTitles[testIndex] + "\" >"); 1138 for (var curves in test) { 1139 var c = test[curves]; 1140 var s = "{{"; 1141 for (var i = 0; i < c.length; i += 2) { 1142 s += "{"; 1143 s += c[i] + "," + c[i + 1]; 1144 s += "}"; 1145 if (i + 2 < c.length) { 1146 s += ", "; 1147 } 1148 } 1149 console.log(s + "}},"); 1150 } 1151 console.log("</div>"); 1152 break; 1153 case 'm': 1154 draw_midpoint = (draw_midpoint + 1) % 3; 1155 redraw(); 1156 break; 1157 case 'N': 1158 testIndex += 9; 1159 case 'n': 1160 testIndex = (testIndex + 1) % tests.length; 1161 drawTop(); 1162 break; 1163 case 'o': 1164 draw_order ^= true; 1165 redraw(); 1166 break; 1167 case 'P': 1168 testIndex -= 9; 1169 case 'p': 1170 if (--testIndex < 0) 1171 testIndex = tests.length - 1; 1172 drawTop(); 1173 break; 1174 case 'q': 1175 draw_quarterpoint = (draw_quarterpoint + 1) % 3; 1176 redraw(); 1177 break; 1178 case 'r': 1179 for (var i = 0; i < testDivs.length; ++i) { 1180 var title = testDivs[i].id.toString(); 1181 if (title == testTitles[testIndex]) { 1182 var str = testDivs[i].firstChild.data; 1183 parse(str, title); 1184 var original = tests.pop(); 1185 testTitles.pop(); 1186 tests[testIndex] = original; 1187 break; 1188 } 1189 } 1190 redraw(); 1191 break; 1192 case 's': 1193 draw_sortpoint = (draw_sortpoint + 1) % 3; 1194 redraw(); 1195 break; 1196 case 't': 1197 draw_t ^= true; 1198 redraw(); 1199 break; 1200 case 'u': 1201 draw_closest_t ^= true; 1202 redraw(); 1203 break; 1204 case 'v': 1205 draw_tangents = (draw_tangents + 1) % 4; 1206 redraw(); 1207 break; 1208 case 'w': 1209 ++curveW; 1210 var choice = 0; 1211 draw_w = false; 1212 for (var curves in tests[testIndex]) { 1213 var curve = tests[testIndex][curves]; 1214 if (curve.length != 7) { 1215 continue; 1216 } 1217 if (choice == curveW) { 1218 draw_w = true; 1219 break; 1220 } 1221 ++choice; 1222 } 1223 if (!draw_w) { 1224 curveW = -1; 1225 } 1226 redraw(); 1227 break; 1228 case 'x': 1229 draw_point_xy ^= true; 1230 redraw(); 1231 break; 1232 case 'y': 1233 draw_mouse_xy ^= true; 1234 redraw(); 1235 break; 1236 case '\\': 1237 retina_scale ^= true; 1238 drawTop(); 1239 break; 1240 case '`': 1241 ++focus_on_selection; 1242 if (focus_on_selection >= tests[testIndex].length) { 1243 focus_on_selection = 0; 1244 } 1245 setScale(xmin, xmax, ymin, ymax); 1246 redraw(); 1247 break; 1248 case '.': 1249 draw_id = (draw_id + 1) % 3; 1250 redraw(); 1251 break; 1252 } 1253 } 1254 1255 function doKeyDown(evt) { 1256 var char = evt.keyCode; 1257 var preventDefault = false; 1258 switch (char) { 1259 case 37: // left arrow 1260 if (evt.shiftKey) { 1261 testIndex -= 9; 1262 } 1263 if (--testIndex < 0) 1264 testIndex = tests.length - 1; 1265 if (evt.ctrlKey) { 1266 redraw(); 1267 } else { 1268 drawTop(); 1269 } 1270 preventDefault = true; 1271 break; 1272 case 39: // right arrow 1273 if (evt.shiftKey) { 1274 testIndex += 9; 1275 } 1276 if (++testIndex >= tests.length) 1277 testIndex = 0; 1278 if (evt.ctrlKey) { 1279 redraw(); 1280 } else { 1281 drawTop(); 1282 } 1283 preventDefault = true; 1284 break; 1285 } 1286 if (preventDefault) { 1287 evt.preventDefault(); 1288 return false; 1289 } 1290 return true; 1291 } 1292 1293 function calcXY() { 1294 var e = window.event; 1295 var tgt = e.target || e.srcElement; 1296 var left = tgt.offsetLeft; 1297 var top = tgt.offsetTop; 1298 mouseX = (e.clientX - left) / scale + srcLeft; 1299 mouseY = (e.clientY - top) / scale + srcTop; 1300 } 1301 1302 function calcLeftTop() { 1303 srcLeft = mouseX - screenWidth / 2 / scale; 1304 srcTop = mouseY - screenHeight / 2 / scale; 1305 } 1306 1307 function handleMouseClick() { 1308 if ((!draw_t || !ptInTControl()) && (!draw_w || !ptInWControl())) { 1309 calcXY(); 1310 } else { 1311 redraw(); 1312 } 1313 } 1314 1315 function initDown() { 1316 var test = tests[testIndex]; 1317 var bestDistance = 1000000; 1318 activePt = -1; 1319 for (var curves in test) { 1320 var testCurve = test[curves]; 1321 if (testCurve.length != 4 && (testCurve.length < 6 || testCurve.length > 8)) { 1322 continue; 1323 } 1324 var testMax = testCurve.length == 7 ? 6 : testCurve.length; 1325 for (var i = 0; i < testMax; i += 2) { 1326 var testX = testCurve[i]; 1327 var testY = testCurve[i + 1]; 1328 var dx = testX - mouseX; 1329 var dy = testY - mouseY; 1330 var dist = dx * dx + dy * dy; 1331 if (dist > bestDistance) { 1332 continue; 1333 } 1334 activeCurve = testCurve; 1335 activePt = i; 1336 bestDistance = dist; 1337 } 1338 } 1339 if (activePt >= 0) { 1340 lastX = mouseX; 1341 lastY = mouseY; 1342 } 1343 } 1344 1345 function handleMouseOver() { 1346 calcXY(); 1347 if (draw_mouse_xy) { 1348 var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places); 1349 ctx.beginPath(); 1350 ctx.rect(300, 100, num.length * 6, 10); 1351 ctx.fillStyle = "white"; 1352 ctx.fill(); 1353 ctx.font = "normal 10px Arial"; 1354 ctx.fillStyle = "black"; 1355 ctx.textAlign = "left"; 1356 ctx.fillText(num, 300, 108); 1357 } 1358 if (!mouseDown) { 1359 activePt = -1; 1360 return; 1361 } 1362 if (activePt < 0) { 1363 initDown(); 1364 return; 1365 } 1366 var deltaX = mouseX - lastX; 1367 var deltaY = mouseY - lastY; 1368 lastX = mouseX; 1369 lastY = mouseY; 1370 if (activePt == 0) { 1371 var test = tests[testIndex]; 1372 for (var curves in test) { 1373 var testCurve = test[curves]; 1374 testCurve[0] += deltaX; 1375 testCurve[1] += deltaY; 1376 } 1377 } else { 1378 activeCurve[activePt] += deltaX; 1379 activeCurve[activePt + 1] += deltaY; 1380 } 1381 redraw(); 1382 } 1383 1384 function start() { 1385 for (var i = 0; i < testDivs.length; ++i) { 1386 var title = testDivs[i].id.toString(); 1387 var str = testDivs[i].firstChild.data; 1388 parse(str, title); 1389 } 1390 drawTop(); 1391 window.addEventListener('keypress', doKeyPress, true); 1392 window.addEventListener('keydown', doKeyDown, true); 1393 window.onresize = function () { 1394 drawTop(); 1395 } 1396 } 1397 1398 </script> 1399 </head> 1400 1401 <body onLoad="start();"> 1402 1403 <canvas id="canvas" width="750" height="500" 1404 onmousedown="mouseDown = true" 1405 onmouseup="mouseDown = false" 1406 onmousemove="handleMouseOver()" 1407 onclick="handleMouseClick()" 1408 ></canvas > 1409 </body> 1410 </html>