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