pathops_sorter.htm 49 KB

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