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