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