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