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