All user data for FoundryVTT. Includes worlds, systems, modules, and any asset in the "foundryuserdata" directory. Does NOT include the FoundryVTT installation itself.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1921 lines
43 KiB

1 year ago
  1. // math-inlining.
  2. const { abs, cos, sin, acos, atan2, sqrt, pow } = Math;
  3. // cube root function yielding real roots
  4. function crt(v) {
  5. return v < 0 ? -pow(-v, 1 / 3) : pow(v, 1 / 3);
  6. }
  7. // trig constants
  8. const pi = Math.PI,
  9. tau = 2 * pi,
  10. quart = pi / 2,
  11. // float precision significant decimal
  12. epsilon = 0.000001,
  13. // extremas used in bbox calculation and similar algorithms
  14. nMax = Number.MAX_SAFE_INTEGER || 9007199254740991,
  15. nMin = Number.MIN_SAFE_INTEGER || -9007199254740991,
  16. // a zero coordinate, which is surprisingly useful
  17. ZERO = { x: 0, y: 0, z: 0 };
  18. // Bezier utility functions
  19. const utils = {
  20. // Legendre-Gauss abscissae with n=24 (x_i values, defined at i=n as the roots of the nth order Legendre polynomial Pn(x))
  21. Tvalues: [
  22. -0.0640568928626056260850430826247450385909,
  23. 0.0640568928626056260850430826247450385909,
  24. -0.1911188674736163091586398207570696318404,
  25. 0.1911188674736163091586398207570696318404,
  26. -0.3150426796961633743867932913198102407864,
  27. 0.3150426796961633743867932913198102407864,
  28. -0.4337935076260451384870842319133497124524,
  29. 0.4337935076260451384870842319133497124524,
  30. -0.5454214713888395356583756172183723700107,
  31. 0.5454214713888395356583756172183723700107,
  32. -0.6480936519369755692524957869107476266696,
  33. 0.6480936519369755692524957869107476266696,
  34. -0.7401241915785543642438281030999784255232,
  35. 0.7401241915785543642438281030999784255232,
  36. -0.8200019859739029219539498726697452080761,
  37. 0.8200019859739029219539498726697452080761,
  38. -0.8864155270044010342131543419821967550873,
  39. 0.8864155270044010342131543419821967550873,
  40. -0.9382745520027327585236490017087214496548,
  41. 0.9382745520027327585236490017087214496548,
  42. -0.9747285559713094981983919930081690617411,
  43. 0.9747285559713094981983919930081690617411,
  44. -0.9951872199970213601799974097007368118745,
  45. 0.9951872199970213601799974097007368118745,
  46. ],
  47. // Legendre-Gauss weights with n=24 (w_i values, defined by a function linked to in the Bezier primer article)
  48. Cvalues: [
  49. 0.1279381953467521569740561652246953718517,
  50. 0.1279381953467521569740561652246953718517,
  51. 0.1258374563468282961213753825111836887264,
  52. 0.1258374563468282961213753825111836887264,
  53. 0.121670472927803391204463153476262425607,
  54. 0.121670472927803391204463153476262425607,
  55. 0.1155056680537256013533444839067835598622,
  56. 0.1155056680537256013533444839067835598622,
  57. 0.1074442701159656347825773424466062227946,
  58. 0.1074442701159656347825773424466062227946,
  59. 0.0976186521041138882698806644642471544279,
  60. 0.0976186521041138882698806644642471544279,
  61. 0.086190161531953275917185202983742667185,
  62. 0.086190161531953275917185202983742667185,
  63. 0.0733464814110803057340336152531165181193,
  64. 0.0733464814110803057340336152531165181193,
  65. 0.0592985849154367807463677585001085845412,
  66. 0.0592985849154367807463677585001085845412,
  67. 0.0442774388174198061686027482113382288593,
  68. 0.0442774388174198061686027482113382288593,
  69. 0.0285313886289336631813078159518782864491,
  70. 0.0285313886289336631813078159518782864491,
  71. 0.0123412297999871995468056670700372915759,
  72. 0.0123412297999871995468056670700372915759,
  73. ],
  74. arcfn: function (t, derivativeFn) {
  75. const d = derivativeFn(t);
  76. let l = d.x * d.x + d.y * d.y;
  77. if (typeof d.z !== "undefined") {
  78. l += d.z * d.z;
  79. }
  80. return sqrt(l);
  81. },
  82. compute: function (t, points, _3d) {
  83. // shortcuts
  84. if (t === 0) {
  85. points[0].t = 0;
  86. return points[0];
  87. }
  88. const order = points.length - 1;
  89. if (t === 1) {
  90. points[order].t = 1;
  91. return points[order];
  92. }
  93. const mt = 1 - t;
  94. let p = points;
  95. // constant?
  96. if (order === 0) {
  97. points[0].t = t;
  98. return points[0];
  99. }
  100. // linear?
  101. if (order === 1) {
  102. const ret = {
  103. x: mt * p[0].x + t * p[1].x,
  104. y: mt * p[0].y + t * p[1].y,
  105. t: t,
  106. };
  107. if (_3d) {
  108. ret.z = mt * p[0].z + t * p[1].z;
  109. }
  110. return ret;
  111. }
  112. // quadratic/cubic curve?
  113. if (order < 4) {
  114. let mt2 = mt * mt,
  115. t2 = t * t,
  116. a,
  117. b,
  118. c,
  119. d = 0;
  120. if (order === 2) {
  121. p = [p[0], p[1], p[2], ZERO];
  122. a = mt2;
  123. b = mt * t * 2;
  124. c = t2;
  125. } else if (order === 3) {
  126. a = mt2 * mt;
  127. b = mt2 * t * 3;
  128. c = mt * t2 * 3;
  129. d = t * t2;
  130. }
  131. const ret = {
  132. x: a * p[0].x + b * p[1].x + c * p[2].x + d * p[3].x,
  133. y: a * p[0].y + b * p[1].y + c * p[2].y + d * p[3].y,
  134. t: t,
  135. };
  136. if (_3d) {
  137. ret.z = a * p[0].z + b * p[1].z + c * p[2].z + d * p[3].z;
  138. }
  139. return ret;
  140. }
  141. // higher order curves: use de Casteljau's computation
  142. const dCpts = JSON.parse(JSON.stringify(points));
  143. while (dCpts.length > 1) {
  144. for (let i = 0; i < dCpts.length - 1; i++) {
  145. dCpts[i] = {
  146. x: dCpts[i].x + (dCpts[i + 1].x - dCpts[i].x) * t,
  147. y: dCpts[i].y + (dCpts[i + 1].y - dCpts[i].y) * t,
  148. };
  149. if (typeof dCpts[i].z !== "undefined") {
  150. dCpts[i] = dCpts[i].z + (dCpts[i + 1].z - dCpts[i].z) * t;
  151. }
  152. }
  153. dCpts.splice(dCpts.length - 1, 1);
  154. }
  155. dCpts[0].t = t;
  156. return dCpts[0];
  157. },
  158. computeWithRatios: function (t, points, ratios, _3d) {
  159. const mt = 1 - t,
  160. r = ratios,
  161. p = points;
  162. let f1 = r[0],
  163. f2 = r[1],
  164. f3 = r[2],
  165. f4 = r[3],
  166. d;
  167. // spec for linear
  168. f1 *= mt;
  169. f2 *= t;
  170. if (p.length === 2) {
  171. d = f1 + f2;
  172. return {
  173. x: (f1 * p[0].x + f2 * p[1].x) / d,
  174. y: (f1 * p[0].y + f2 * p[1].y) / d,
  175. z: !_3d ? false : (f1 * p[0].z + f2 * p[1].z) / d,
  176. t: t,
  177. };
  178. }
  179. // upgrade to quadratic
  180. f1 *= mt;
  181. f2 *= 2 * mt;
  182. f3 *= t * t;
  183. if (p.length === 3) {
  184. d = f1 + f2 + f3;
  185. return {
  186. x: (f1 * p[0].x + f2 * p[1].x + f3 * p[2].x) / d,
  187. y: (f1 * p[0].y + f2 * p[1].y + f3 * p[2].y) / d,
  188. z: !_3d ? false : (f1 * p[0].z + f2 * p[1].z + f3 * p[2].z) / d,
  189. t: t,
  190. };
  191. }
  192. // upgrade to cubic
  193. f1 *= mt;
  194. f2 *= 1.5 * mt;
  195. f3 *= 3 * mt;
  196. f4 *= t * t * t;
  197. if (p.length === 4) {
  198. d = f1 + f2 + f3 + f4;
  199. return {
  200. x: (f1 * p[0].x + f2 * p[1].x + f3 * p[2].x + f4 * p[3].x) / d,
  201. y: (f1 * p[0].y + f2 * p[1].y + f3 * p[2].y + f4 * p[3].y) / d,
  202. z: !_3d
  203. ? false
  204. : (f1 * p[0].z + f2 * p[1].z + f3 * p[2].z + f4 * p[3].z) / d,
  205. t: t,
  206. };
  207. }
  208. },
  209. derive: function (points, _3d) {
  210. const dpoints = [];
  211. for (let p = points, d = p.length, c = d - 1; d > 1; d--, c--) {
  212. const list = [];
  213. for (let j = 0, dpt; j < c; j++) {
  214. dpt = {
  215. x: c * (p[j + 1].x - p[j].x),
  216. y: c * (p[j + 1].y - p[j].y),
  217. };
  218. if (_3d) {
  219. dpt.z = c * (p[j + 1].z - p[j].z);
  220. }
  221. list.push(dpt);
  222. }
  223. dpoints.push(list);
  224. p = list;
  225. }
  226. return dpoints;
  227. },
  228. between: function (v, m, M) {
  229. return (
  230. (m <= v && v <= M) ||
  231. utils.approximately(v, m) ||
  232. utils.approximately(v, M)
  233. );
  234. },
  235. approximately: function (a, b, precision) {
  236. return abs(a - b) <= (precision || epsilon);
  237. },
  238. length: function (derivativeFn) {
  239. const z = 0.5,
  240. len = utils.Tvalues.length;
  241. let sum = 0;
  242. for (let i = 0, t; i < len; i++) {
  243. t = z * utils.Tvalues[i] + z;
  244. sum += utils.Cvalues[i] * utils.arcfn(t, derivativeFn);
  245. }
  246. return z * sum;
  247. },
  248. map: function (v, ds, de, ts, te) {
  249. const d1 = de - ds,
  250. d2 = te - ts,
  251. v2 = v - ds,
  252. r = v2 / d1;
  253. return ts + d2 * r;
  254. },
  255. lerp: function (r, v1, v2) {
  256. const ret = {
  257. x: v1.x + r * (v2.x - v1.x),
  258. y: v1.y + r * (v2.y - v1.y),
  259. };
  260. if (!!v1.z && !!v2.z) {
  261. ret.z = v1.z + r * (v2.z - v1.z);
  262. }
  263. return ret;
  264. },
  265. pointToString: function (p) {
  266. let s = p.x + "/" + p.y;
  267. if (typeof p.z !== "undefined") {
  268. s += "/" + p.z;
  269. }
  270. return s;
  271. },
  272. pointsToString: function (points) {
  273. return "[" + points.map(utils.pointToString).join(", ") + "]";
  274. },
  275. copy: function (obj) {
  276. return JSON.parse(JSON.stringify(obj));
  277. },
  278. angle: function (o, v1, v2) {
  279. const dx1 = v1.x - o.x,
  280. dy1 = v1.y - o.y,
  281. dx2 = v2.x - o.x,
  282. dy2 = v2.y - o.y,
  283. cross = dx1 * dy2 - dy1 * dx2,
  284. dot = dx1 * dx2 + dy1 * dy2;
  285. return atan2(cross, dot);
  286. },
  287. // round as string, to avoid rounding errors
  288. round: function (v, d) {
  289. const s = "" + v;
  290. const pos = s.indexOf(".");
  291. return parseFloat(s.substring(0, pos + 1 + d));
  292. },
  293. dist: function (p1, p2) {
  294. const dx = p1.x - p2.x,
  295. dy = p1.y - p2.y;
  296. return sqrt(dx * dx + dy * dy);
  297. },
  298. closest: function (LUT, point) {
  299. let mdist = pow(2, 63),
  300. mpos,
  301. d;
  302. LUT.forEach(function (p, idx) {
  303. d = utils.dist(point, p);
  304. if (d < mdist) {
  305. mdist = d;
  306. mpos = idx;
  307. }
  308. });
  309. return { mdist: mdist, mpos: mpos };
  310. },
  311. abcratio: function (t, n) {
  312. // see ratio(t) note on http://pomax.github.io/bezierinfo/#abc
  313. if (n !== 2 && n !== 3) {
  314. return false;
  315. }
  316. if (typeof t === "undefined") {
  317. t = 0.5;
  318. } else if (t === 0 || t === 1) {
  319. return t;
  320. }
  321. const bottom = pow(t, n) + pow(1 - t, n),
  322. top = bottom - 1;
  323. return abs(top / bottom);
  324. },
  325. projectionratio: function (t, n) {
  326. // see u(t) note on http://pomax.github.io/bezierinfo/#abc
  327. if (n !== 2 && n !== 3) {
  328. return false;
  329. }
  330. if (typeof t === "undefined") {
  331. t = 0.5;
  332. } else if (t === 0 || t === 1) {
  333. return t;
  334. }
  335. const top = pow(1 - t, n),
  336. bottom = pow(t, n) + top;
  337. return top / bottom;
  338. },
  339. lli8: function (x1, y1, x2, y2, x3, y3, x4, y4) {
  340. const nx =
  341. (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
  342. ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
  343. d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
  344. if (d == 0) {
  345. return false;
  346. }
  347. return { x: nx / d, y: ny / d };
  348. },
  349. lli4: function (p1, p2, p3, p4) {
  350. const x1 = p1.x,
  351. y1 = p1.y,
  352. x2 = p2.x,
  353. y2 = p2.y,
  354. x3 = p3.x,
  355. y3 = p3.y,
  356. x4 = p4.x,
  357. y4 = p4.y;
  358. return utils.lli8(x1, y1, x2, y2, x3, y3, x4, y4);
  359. },
  360. lli: function (v1, v2) {
  361. return utils.lli4(v1, v1.c, v2, v2.c);
  362. },
  363. makeline: function (p1, p2) {
  364. const x1 = p1.x,
  365. y1 = p1.y,
  366. x2 = p2.x,
  367. y2 = p2.y,
  368. dx = (x2 - x1) / 3,
  369. dy = (y2 - y1) / 3;
  370. return new Bezier(
  371. x1,
  372. y1,
  373. x1 + dx,
  374. y1 + dy,
  375. x1 + 2 * dx,
  376. y1 + 2 * dy,
  377. x2,
  378. y2
  379. );
  380. },
  381. findbbox: function (sections) {
  382. let mx = nMax,
  383. my = nMax,
  384. MX = nMin,
  385. MY = nMin;
  386. sections.forEach(function (s) {
  387. const bbox = s.bbox();
  388. if (mx > bbox.x.min) mx = bbox.x.min;
  389. if (my > bbox.y.min) my = bbox.y.min;
  390. if (MX < bbox.x.max) MX = bbox.x.max;
  391. if (MY < bbox.y.max) MY = bbox.y.max;
  392. });
  393. return {
  394. x: { min: mx, mid: (mx + MX) / 2, max: MX, size: MX - mx },
  395. y: { min: my, mid: (my + MY) / 2, max: MY, size: MY - my },
  396. };
  397. },
  398. shapeintersections: function (
  399. s1,
  400. bbox1,
  401. s2,
  402. bbox2,
  403. curveIntersectionThreshold
  404. ) {
  405. if (!utils.bboxoverlap(bbox1, bbox2)) return [];
  406. const intersections = [];
  407. const a1 = [s1.startcap, s1.forward, s1.back, s1.endcap];
  408. const a2 = [s2.startcap, s2.forward, s2.back, s2.endcap];
  409. a1.forEach(function (l1) {
  410. if (l1.virtual) return;
  411. a2.forEach(function (l2) {
  412. if (l2.virtual) return;
  413. const iss = l1.intersects(l2, curveIntersectionThreshold);
  414. if (iss.length > 0) {
  415. iss.c1 = l1;
  416. iss.c2 = l2;
  417. iss.s1 = s1;
  418. iss.s2 = s2;
  419. intersections.push(iss);
  420. }
  421. });
  422. });
  423. return intersections;
  424. },
  425. makeshape: function (forward, back, curveIntersectionThreshold) {
  426. const bpl = back.points.length;
  427. const fpl = forward.points.length;
  428. const start = utils.makeline(back.points[bpl - 1], forward.points[0]);
  429. const end = utils.makeline(forward.points[fpl - 1], back.points[0]);
  430. const shape = {
  431. startcap: start,
  432. forward: forward,
  433. back: back,
  434. endcap: end,
  435. bbox: utils.findbbox([start, forward, back, end]),
  436. };
  437. shape.intersections = function (s2) {
  438. return utils.shapeintersections(
  439. shape,
  440. shape.bbox,
  441. s2,
  442. s2.bbox,
  443. curveIntersectionThreshold
  444. );
  445. };
  446. return shape;
  447. },
  448. getminmax: function (curve, d, list) {
  449. if (!list) return { min: 0, max: 0 };
  450. let min = nMax,
  451. max = nMin,
  452. t,
  453. c;
  454. if (list.indexOf(0) === -1) {
  455. list = [0].concat(list);
  456. }
  457. if (list.indexOf(1) === -1) {
  458. list.push(1);
  459. }
  460. for (let i = 0, len = list.length; i < len; i++) {
  461. t = list[i];
  462. c = curve.get(t);
  463. if (c[d] < min) {
  464. min = c[d];
  465. }
  466. if (c[d] > max) {
  467. max = c[d];
  468. }
  469. }
  470. return { min: min, mid: (min + max) / 2, max: max, size: max - min };
  471. },
  472. align: function (points, line) {
  473. const tx = line.p1.x,
  474. ty = line.p1.y,
  475. a = -atan2(line.p2.y - ty, line.p2.x - tx),
  476. d = function (v) {
  477. return {
  478. x: (v.x - tx) * cos(a) - (v.y - ty) * sin(a),
  479. y: (v.x - tx) * sin(a) + (v.y - ty) * cos(a),
  480. };
  481. };
  482. return points.map(d);
  483. },
  484. roots: function (points, line) {
  485. line = line || { p1: { x: 0, y: 0 }, p2: { x: 1, y: 0 } };
  486. const order = points.length - 1;
  487. const aligned = utils.align(points, line);
  488. const reduce = function (t) {
  489. return 0 <= t && t <= 1;
  490. };
  491. if (order === 2) {
  492. const a = aligned[0].y,
  493. b = aligned[1].y,
  494. c = aligned[2].y,
  495. d = a - 2 * b + c;
  496. if (d !== 0) {
  497. const m1 = -sqrt(b * b - a * c),
  498. m2 = -a + b,
  499. v1 = -(m1 + m2) / d,
  500. v2 = -(-m1 + m2) / d;
  501. return [v1, v2].filter(reduce);
  502. } else if (b !== c && d === 0) {
  503. return [(2 * b - c) / (2 * b - 2 * c)].filter(reduce);
  504. }
  505. return [];
  506. }
  507. // see http://www.trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm
  508. const pa = aligned[0].y,
  509. pb = aligned[1].y,
  510. pc = aligned[2].y,
  511. pd = aligned[3].y;
  512. let d = -pa + 3 * pb - 3 * pc + pd,
  513. a = 3 * pa - 6 * pb + 3 * pc,
  514. b = -3 * pa + 3 * pb,
  515. c = pa;
  516. if (utils.approximately(d, 0)) {
  517. // this is not a cubic curve.
  518. if (utils.approximately(a, 0)) {
  519. // in fact, this is not a quadratic curve either.
  520. if (utils.approximately(b, 0)) {
  521. // in fact in fact, there are no solutions.
  522. return [];
  523. }
  524. // linear solution:
  525. return [-c / b].filter(reduce);
  526. }
  527. // quadratic solution:
  528. const q = sqrt(b * b - 4 * a * c),
  529. a2 = 2 * a;
  530. return [(q - b) / a2, (-b - q) / a2].filter(reduce);
  531. }
  532. // at this point, we know we need a cubic solution:
  533. a /= d;
  534. b /= d;
  535. c /= d;
  536. const p = (3 * b - a * a) / 3,
  537. p3 = p / 3,
  538. q = (2 * a * a * a - 9 * a * b + 27 * c) / 27,
  539. q2 = q / 2,
  540. discriminant = q2 * q2 + p3 * p3 * p3;
  541. let u1, v1, x1, x2, x3;
  542. if (discriminant < 0) {
  543. const mp3 = -p / 3,
  544. mp33 = mp3 * mp3 * mp3,
  545. r = sqrt(mp33),
  546. t = -q / (2 * r),
  547. cosphi = t < -1 ? -1 : t > 1 ? 1 : t,
  548. phi = acos(cosphi),
  549. crtr = crt(r),
  550. t1 = 2 * crtr;
  551. x1 = t1 * cos(phi / 3) - a / 3;
  552. x2 = t1 * cos((phi + tau) / 3) - a / 3;
  553. x3 = t1 * cos((phi + 2 * tau) / 3) - a / 3;
  554. return [x1, x2, x3].filter(reduce);
  555. } else if (discriminant === 0) {
  556. u1 = q2 < 0 ? crt(-q2) : -crt(q2);
  557. x1 = 2 * u1 - a / 3;
  558. x2 = -u1 - a / 3;
  559. return [x1, x2].filter(reduce);
  560. } else {
  561. const sd = sqrt(discriminant);
  562. u1 = crt(-q2 + sd);
  563. v1 = crt(q2 + sd);
  564. return [u1 - v1 - a / 3].filter(reduce);
  565. }
  566. },
  567. droots: function (p) {
  568. // quadratic roots are easy
  569. if (p.length === 3) {
  570. const a = p[0],
  571. b = p[1],
  572. c = p[2],
  573. d = a - 2 * b + c;
  574. if (d !== 0) {
  575. const m1 = -sqrt(b * b - a * c),
  576. m2 = -a + b,
  577. v1 = -(m1 + m2) / d,
  578. v2 = -(-m1 + m2) / d;
  579. return [v1, v2];
  580. } else if (b !== c && d === 0) {
  581. return [(2 * b - c) / (2 * (b - c))];
  582. }
  583. return [];
  584. }
  585. // linear roots are even easier
  586. if (p.length === 2) {
  587. const a = p[0],
  588. b = p[1];
  589. if (a !== b) {
  590. return [a / (a - b)];
  591. }
  592. return [];
  593. }
  594. return [];
  595. },
  596. curvature: function (t, d1, d2, _3d, kOnly) {
  597. let num,
  598. dnm,
  599. adk,
  600. dk,
  601. k = 0,
  602. r = 0;
  603. //
  604. // We're using the following formula for curvature:
  605. //
  606. // x'y" - y'x"
  607. // k(t) = ------------------
  608. // (x'² + y'²)^(3/2)
  609. //
  610. // from https://en.wikipedia.org/wiki/Radius_of_curvature#Definition
  611. //
  612. // With it corresponding 3D counterpart:
  613. //
  614. // sqrt( (y'z" - y"z')² + (z'x" - z"x')² + (x'y" - x"y')²)
  615. // k(t) = -------------------------------------------------------
  616. // (x'² + y'² + z'²)^(3/2)
  617. //
  618. const d = utils.compute(t, d1);
  619. const dd = utils.compute(t, d2);
  620. const qdsum = d.x * d.x + d.y * d.y;
  621. if (_3d) {
  622. num = sqrt(
  623. pow(d.y * dd.z - dd.y * d.z, 2) +
  624. pow(d.z * dd.x - dd.z * d.x, 2) +
  625. pow(d.x * dd.y - dd.x * d.y, 2)
  626. );
  627. dnm = pow(qdsum + d.z * d.z, 3 / 2);
  628. } else {
  629. num = d.x * dd.y - d.y * dd.x;
  630. dnm = pow(qdsum, 3 / 2);
  631. }
  632. if (num === 0 || dnm === 0) {
  633. return { k: 0, r: 0 };
  634. }
  635. k = num / dnm;
  636. r = dnm / num;
  637. // We're also computing the derivative of kappa, because
  638. // there is value in knowing the rate of change for the
  639. // curvature along the curve. And we're just going to
  640. // ballpark it based on an epsilon.
  641. if (!kOnly) {
  642. // compute k'(t) based on the interval before, and after it,
  643. // to at least try to not introduce forward/backward pass bias.
  644. const pk = utils.curvature(t - 0.001, d1, d2, _3d, true).k;
  645. const nk = utils.curvature(t + 0.001, d1, d2, _3d, true).k;
  646. dk = (nk - k + (k - pk)) / 2;
  647. adk = (abs(nk - k) + abs(k - pk)) / 2;
  648. }
  649. return { k: k, r: r, dk: dk, adk: adk };
  650. },
  651. inflections: function (points) {
  652. if (points.length < 4) return [];
  653. // FIXME: TODO: add in inflection abstraction for quartic+ curves?
  654. const p = utils.align(points, { p1: points[0], p2: points.slice(-1)[0] }),
  655. a = p[2].x * p[1].y,
  656. b = p[3].x * p[1].y,
  657. c = p[1].x * p[2].y,
  658. d = p[3].x * p[2].y,
  659. v1 = 18 * (-3 * a + 2 * b + 3 * c - d),
  660. v2 = 18 * (3 * a - b - 3 * c),
  661. v3 = 18 * (c - a);
  662. if (utils.approximately(v1, 0)) {
  663. if (!utils.approximately(v2, 0)) {
  664. let t = -v3 / v2;
  665. if (0 <= t && t <= 1) return [t];
  666. }
  667. return [];
  668. }
  669. const trm = v2 * v2 - 4 * v1 * v3,
  670. sq = Math.sqrt(trm),
  671. d2 = 2 * v1;
  672. if (utils.approximately(d2, 0)) return [];
  673. return [(sq - v2) / d2, -(v2 + sq) / d2].filter(function (r) {
  674. return 0 <= r && r <= 1;
  675. });
  676. },
  677. bboxoverlap: function (b1, b2) {
  678. const dims = ["x", "y"],
  679. len = dims.length;
  680. for (let i = 0, dim, l, t, d; i < len; i++) {
  681. dim = dims[i];
  682. l = b1[dim].mid;
  683. t = b2[dim].mid;
  684. d = (b1[dim].size + b2[dim].size) / 2;
  685. if (abs(l - t) >= d) return false;
  686. }
  687. return true;
  688. },
  689. expandbox: function (bbox, _bbox) {
  690. if (_bbox.x.min < bbox.x.min) {
  691. bbox.x.min = _bbox.x.min;
  692. }
  693. if (_bbox.y.min < bbox.y.min) {
  694. bbox.y.min = _bbox.y.min;
  695. }
  696. if (_bbox.z && _bbox.z.min < bbox.z.min) {
  697. bbox.z.min = _bbox.z.min;
  698. }
  699. if (_bbox.x.max > bbox.x.max) {
  700. bbox.x.max = _bbox.x.max;
  701. }
  702. if (_bbox.y.max > bbox.y.max) {
  703. bbox.y.max = _bbox.y.max;
  704. }
  705. if (_bbox.z && _bbox.z.max > bbox.z.max) {
  706. bbox.z.max = _bbox.z.max;
  707. }
  708. bbox.x.mid = (bbox.x.min + bbox.x.max) / 2;
  709. bbox.y.mid = (bbox.y.min + bbox.y.max) / 2;
  710. if (bbox.z) {
  711. bbox.z.mid = (bbox.z.min + bbox.z.max) / 2;
  712. }
  713. bbox.x.size = bbox.x.max - bbox.x.min;
  714. bbox.y.size = bbox.y.max - bbox.y.min;
  715. if (bbox.z) {
  716. bbox.z.size = bbox.z.max - bbox.z.min;
  717. }
  718. },
  719. pairiteration: function (c1, c2, curveIntersectionThreshold) {
  720. const c1b = c1.bbox(),
  721. c2b = c2.bbox(),
  722. r = 100000,
  723. threshold = curveIntersectionThreshold || 0.5;
  724. if (
  725. c1b.x.size + c1b.y.size < threshold &&
  726. c2b.x.size + c2b.y.size < threshold
  727. ) {
  728. return [
  729. (((r * (c1._t1 + c1._t2)) / 2) | 0) / r +
  730. "/" +
  731. (((r * (c2._t1 + c2._t2)) / 2) | 0) / r,
  732. ];
  733. }
  734. let cc1 = c1.split(0.5),
  735. cc2 = c2.split(0.5),
  736. pairs = [
  737. { left: cc1.left, right: cc2.left },
  738. { left: cc1.left, right: cc2.right },
  739. { left: cc1.right, right: cc2.right },
  740. { left: cc1.right, right: cc2.left },
  741. ];
  742. pairs = pairs.filter(function (pair) {
  743. return utils.bboxoverlap(pair.left.bbox(), pair.right.bbox());
  744. });
  745. let results = [];
  746. if (pairs.length === 0) return results;
  747. pairs.forEach(function (pair) {
  748. results = results.concat(
  749. utils.pairiteration(pair.left, pair.right, threshold)
  750. );
  751. });
  752. results = results.filter(function (v, i) {
  753. return results.indexOf(v) === i;
  754. });
  755. return results;
  756. },
  757. getccenter: function (p1, p2, p3) {
  758. const dx1 = p2.x - p1.x,
  759. dy1 = p2.y - p1.y,
  760. dx2 = p3.x - p2.x,
  761. dy2 = p3.y - p2.y,
  762. dx1p = dx1 * cos(quart) - dy1 * sin(quart),
  763. dy1p = dx1 * sin(quart) + dy1 * cos(quart),
  764. dx2p = dx2 * cos(quart) - dy2 * sin(quart),
  765. dy2p = dx2 * sin(quart) + dy2 * cos(quart),
  766. // chord midpoints
  767. mx1 = (p1.x + p2.x) / 2,
  768. my1 = (p1.y + p2.y) / 2,
  769. mx2 = (p2.x + p3.x) / 2,
  770. my2 = (p2.y + p3.y) / 2,
  771. // midpoint offsets
  772. mx1n = mx1 + dx1p,
  773. my1n = my1 + dy1p,
  774. mx2n = mx2 + dx2p,
  775. my2n = my2 + dy2p,
  776. // intersection of these lines:
  777. arc = utils.lli8(mx1, my1, mx1n, my1n, mx2, my2, mx2n, my2n),
  778. r = utils.dist(arc, p1);
  779. // arc start/end values, over mid point:
  780. let s = atan2(p1.y - arc.y, p1.x - arc.x),
  781. m = atan2(p2.y - arc.y, p2.x - arc.x),
  782. e = atan2(p3.y - arc.y, p3.x - arc.x),
  783. _;
  784. // determine arc direction (cw/ccw correction)
  785. if (s < e) {
  786. // if s<m<e, arc(s, e)
  787. // if m<s<e, arc(e, s + tau)
  788. // if s<e<m, arc(e, s + tau)
  789. if (s > m || m > e) {
  790. s += tau;
  791. }
  792. if (s > e) {
  793. _ = e;
  794. e = s;
  795. s = _;
  796. }
  797. } else {
  798. // if e<m<s, arc(e, s)
  799. // if m<e<s, arc(s, e + tau)
  800. // if e<s<m, arc(s, e + tau)
  801. if (e < m && m < s) {
  802. _ = e;
  803. e = s;
  804. s = _;
  805. } else {
  806. e += tau;
  807. }
  808. }
  809. // assign and done.
  810. arc.s = s;
  811. arc.e = e;
  812. arc.r = r;
  813. return arc;
  814. },
  815. numberSort: function (a, b) {
  816. return a - b;
  817. },
  818. };
  819. /**
  820. * Poly Bezier
  821. * @param {[type]} curves [description]
  822. */
  823. class PolyBezier {
  824. constructor(curves) {
  825. this.curves = [];
  826. this._3d = false;
  827. if (!!curves) {
  828. this.curves = curves;
  829. this._3d = this.curves[0]._3d;
  830. }
  831. }
  832. valueOf() {
  833. return this.toString();
  834. }
  835. toString() {
  836. return (
  837. "[" +
  838. this.curves
  839. .map(function (curve) {
  840. return utils.pointsToString(curve.points);
  841. })
  842. .join(", ") +
  843. "]"
  844. );
  845. }
  846. addCurve(curve) {
  847. this.curves.push(curve);
  848. this._3d = this._3d || curve._3d;
  849. }
  850. length() {
  851. return this.curves
  852. .map(function (v) {
  853. return v.length();
  854. })
  855. .reduce(function (a, b) {
  856. return a + b;
  857. });
  858. }
  859. curve(idx) {
  860. return this.curves[idx];
  861. }
  862. bbox() {
  863. const c = this.curves;
  864. var bbox = c[0].bbox();
  865. for (var i = 1; i < c.length; i++) {
  866. utils.expandbox(bbox, c[i].bbox());
  867. }
  868. return bbox;
  869. }
  870. offset(d) {
  871. const offset = [];
  872. this.curves.forEach(function (v) {
  873. offset.push(...v.offset(d));
  874. });
  875. return new PolyBezier(offset);
  876. }
  877. }
  878. /**
  879. A javascript Bezier curve library by Pomax.
  880. Based on http://pomax.github.io/bezierinfo
  881. This code is MIT licensed.
  882. **/
  883. // math-inlining.
  884. const { abs: abs$1, min, max, cos: cos$1, sin: sin$1, acos: acos$1, sqrt: sqrt$1 } = Math;
  885. const pi$1 = Math.PI;
  886. /**
  887. * Bezier curve constructor.
  888. *
  889. * ...docs pending...
  890. */
  891. class Bezier {
  892. constructor(coords) {
  893. let args =
  894. coords && coords.forEach ? coords : Array.from(arguments).slice();
  895. let coordlen = false;
  896. if (typeof args[0] === "object") {
  897. coordlen = args.length;
  898. const newargs = [];
  899. args.forEach(function (point) {
  900. ["x", "y", "z"].forEach(function (d) {
  901. if (typeof point[d] !== "undefined") {
  902. newargs.push(point[d]);
  903. }
  904. });
  905. });
  906. args = newargs;
  907. }
  908. let higher = false;
  909. const len = args.length;
  910. if (coordlen) {
  911. if (coordlen > 4) {
  912. if (arguments.length !== 1) {
  913. throw new Error(
  914. "Only new Bezier(point[]) is accepted for 4th and higher order curves"
  915. );
  916. }
  917. higher = true;
  918. }
  919. } else {
  920. if (len !== 6 && len !== 8 && len !== 9 && len !== 12) {
  921. if (arguments.length !== 1) {
  922. throw new Error(
  923. "Only new Bezier(point[]) is accepted for 4th and higher order curves"
  924. );
  925. }
  926. }
  927. }
  928. const _3d = (this._3d =
  929. (!higher && (len === 9 || len === 12)) ||
  930. (coords && coords[0] && typeof coords[0].z !== "undefined"));
  931. const points = (this.points = []);
  932. for (let idx = 0, step = _3d ? 3 : 2; idx < len; idx += step) {
  933. var point = {
  934. x: args[idx],
  935. y: args[idx + 1],
  936. };
  937. if (_3d) {
  938. point.z = args[idx + 2];
  939. }
  940. points.push(point);
  941. }
  942. const order = (this.order = points.length - 1);
  943. const dims = (this.dims = ["x", "y"]);
  944. if (_3d) dims.push("z");
  945. this.dimlen = dims.length;
  946. const aligned = utils.align(points, { p1: points[0], p2: points[order] });
  947. this._linear = !aligned.some((p) => abs$1(p.y) > 0.0001);
  948. this._lut = [];
  949. this._t1 = 0;
  950. this._t2 = 1;
  951. this.update();
  952. }
  953. static quadraticFromPoints(p1, p2, p3, t) {
  954. if (typeof t === "undefined") {
  955. t = 0.5;
  956. }
  957. // shortcuts, although they're really dumb
  958. if (t === 0) {
  959. return new Bezier(p2, p2, p3);
  960. }
  961. if (t === 1) {
  962. return new Bezier(p1, p2, p2);
  963. }
  964. // real fitting.
  965. const abc = Bezier.getABC(2, p1, p2, p3, t);
  966. return new Bezier(p1, abc.A, p3);
  967. }
  968. static cubicFromPoints(S, B, E, t, d1) {
  969. if (typeof t === "undefined") {
  970. t = 0.5;
  971. }
  972. const abc = Bezier.getABC(3, S, B, E, t);
  973. if (typeof d1 === "undefined") {
  974. d1 = utils.dist(B, abc.C);
  975. }
  976. const d2 = (d1 * (1 - t)) / t;
  977. const selen = utils.dist(S, E),
  978. lx = (E.x - S.x) / selen,
  979. ly = (E.y - S.y) / selen,
  980. bx1 = d1 * lx,
  981. by1 = d1 * ly,
  982. bx2 = d2 * lx,
  983. by2 = d2 * ly;
  984. // derivation of new hull coordinates
  985. const e1 = { x: B.x - bx1, y: B.y - by1 },
  986. e2 = { x: B.x + bx2, y: B.y + by2 },
  987. A = abc.A,
  988. v1 = { x: A.x + (e1.x - A.x) / (1 - t), y: A.y + (e1.y - A.y) / (1 - t) },
  989. v2 = { x: A.x + (e2.x - A.x) / t, y: A.y + (e2.y - A.y) / t },
  990. nc1 = { x: S.x + (v1.x - S.x) / t, y: S.y + (v1.y - S.y) / t },
  991. nc2 = {
  992. x: E.x + (v2.x - E.x) / (1 - t),
  993. y: E.y + (v2.y - E.y) / (1 - t),
  994. };
  995. // ...done
  996. return new Bezier(S, nc1, nc2, E);
  997. }
  998. static getUtils() {
  999. return utils;
  1000. }
  1001. getUtils() {
  1002. return Bezier.getUtils();
  1003. }
  1004. static get PolyBezier() {
  1005. return PolyBezier;
  1006. }
  1007. valueOf() {
  1008. return this.toString();
  1009. }
  1010. toString() {
  1011. return utils.pointsToString(this.points);
  1012. }
  1013. toSVG() {
  1014. if (this._3d) return false;
  1015. const p = this.points,
  1016. x = p[0].x,
  1017. y = p[0].y,
  1018. s = ["M", x, y, this.order === 2 ? "Q" : "C"];
  1019. for (let i = 1, last = p.length; i < last; i++) {
  1020. s.push(p[i].x);
  1021. s.push(p[i].y);
  1022. }
  1023. return s.join(" ");
  1024. }
  1025. setRatios(ratios) {
  1026. if (ratios.length !== this.points.length) {
  1027. throw new Error("incorrect number of ratio values");
  1028. }
  1029. this.ratios = ratios;
  1030. this._lut = []; // invalidate any precomputed LUT
  1031. }
  1032. verify() {
  1033. const print = this.coordDigest();
  1034. if (print !== this._print) {
  1035. this._print = print;
  1036. this.update();
  1037. }
  1038. }
  1039. coordDigest() {
  1040. return this.points
  1041. .map(function (c, pos) {
  1042. return "" + pos + c.x + c.y + (c.z ? c.z : 0);
  1043. })
  1044. .join("");
  1045. }
  1046. update() {
  1047. // invalidate any precomputed LUT
  1048. this._lut = [];
  1049. this.dpoints = utils.derive(this.points, this._3d);
  1050. this.computedirection();
  1051. }
  1052. computedirection() {
  1053. const points = this.points;
  1054. const angle = utils.angle(points[0], points[this.order], points[1]);
  1055. this.clockwise = angle > 0;
  1056. }
  1057. length() {
  1058. return utils.length(this.derivative.bind(this));
  1059. }
  1060. static getABC(order = 2, S, B, E, t = 0.5) {
  1061. const u = utils.projectionratio(t, order),
  1062. um = 1 - u,
  1063. C = {
  1064. x: u * S.x + um * E.x,
  1065. y: u * S.y + um * E.y,
  1066. },
  1067. s = utils.abcratio(t, order),
  1068. A = {
  1069. x: B.x + (B.x - C.x) / s,
  1070. y: B.y + (B.y - C.y) / s,
  1071. };
  1072. return { A, B, C, S, E };
  1073. }
  1074. getABC(t, B) {
  1075. B = B || this.get(t);
  1076. let S = this.points[0];
  1077. let E = this.points[this.order];
  1078. return Bezier.getABC(this.order, S, B, E, t);
  1079. }
  1080. getLUT(steps) {
  1081. this.verify();
  1082. steps = steps || 100;
  1083. if (this._lut.length === steps) {
  1084. return this._lut;
  1085. }
  1086. this._lut = [];
  1087. // We want a range from 0 to 1 inclusive, so
  1088. // we decrement and then use <= rather than <:
  1089. steps--;
  1090. for (let i = 0, p, t; i < steps; i++) {
  1091. t = i / (steps - 1);
  1092. p = this.compute(t);
  1093. p.t = t;
  1094. this._lut.push(p);
  1095. }
  1096. return this._lut;
  1097. }
  1098. on(point, error) {
  1099. error = error || 5;
  1100. const lut = this.getLUT(),
  1101. hits = [];
  1102. for (let i = 0, c, t = 0; i < lut.length; i++) {
  1103. c = lut[i];
  1104. if (utils.dist(c, point) < error) {
  1105. hits.push(c);
  1106. t += i / lut.length;
  1107. }
  1108. }
  1109. if (!hits.length) return false;
  1110. return (t /= hits.length);
  1111. }
  1112. project(point) {
  1113. // step 1: coarse check
  1114. const LUT = this.getLUT(),
  1115. l = LUT.length - 1,
  1116. closest = utils.closest(LUT, point),
  1117. mpos = closest.mpos,
  1118. t1 = (mpos - 1) / l,
  1119. t2 = (mpos + 1) / l,
  1120. step = 0.1 / l;
  1121. // step 2: fine check
  1122. let mdist = closest.mdist,
  1123. t = t1,
  1124. ft = t,
  1125. p;
  1126. mdist += 1;
  1127. for (let d; t < t2 + step; t += step) {
  1128. p = this.compute(t);
  1129. d = utils.dist(point, p);
  1130. if (d < mdist) {
  1131. mdist = d;
  1132. ft = t;
  1133. }
  1134. }
  1135. ft = ft < 0 ? 0 : ft > 1 ? 1 : ft;
  1136. p = this.compute(ft);
  1137. p.t = ft;
  1138. p.d = mdist;
  1139. return p;
  1140. }
  1141. get(t) {
  1142. return this.compute(t);
  1143. }
  1144. point(idx) {
  1145. return this.points[idx];
  1146. }
  1147. compute(t) {
  1148. if (this.ratios) {
  1149. return utils.computeWithRatios(t, this.points, this.ratios, this._3d);
  1150. }
  1151. return utils.compute(t, this.points, this._3d, this.ratios);
  1152. }
  1153. raise() {
  1154. const p = this.points,
  1155. np = [p[0]],
  1156. k = p.length;
  1157. for (let i = 1, pi, pim; i < k; i++) {
  1158. pi = p[i];
  1159. pim = p[i - 1];
  1160. np[i] = {
  1161. x: ((k - i) / k) * pi.x + (i / k) * pim.x,
  1162. y: ((k - i) / k) * pi.y + (i / k) * pim.y,
  1163. };
  1164. }
  1165. np[k] = p[k - 1];
  1166. return new Bezier(np);
  1167. }
  1168. derivative(t) {
  1169. return utils.compute(t, this.dpoints[0]);
  1170. }
  1171. dderivative(t) {
  1172. return utils.compute(t, this.dpoints[1]);
  1173. }
  1174. align() {
  1175. let p = this.points;
  1176. return new Bezier(utils.align(p, { p1: p[0], p2: p[p.length - 1] }));
  1177. }
  1178. curvature(t) {
  1179. return utils.curvature(t, this.dpoints[0], this.dpoints[1], this._3d);
  1180. }
  1181. inflections() {
  1182. return utils.inflections(this.points);
  1183. }
  1184. normal(t) {
  1185. return this._3d ? this.__normal3(t) : this.__normal2(t);
  1186. }
  1187. __normal2(t) {
  1188. const d = this.derivative(t);
  1189. const q = sqrt$1(d.x * d.x + d.y * d.y);
  1190. return { x: -d.y / q, y: d.x / q };
  1191. }
  1192. __normal3(t) {
  1193. // see http://stackoverflow.com/questions/25453159
  1194. const r1 = this.derivative(t),
  1195. r2 = this.derivative(t + 0.01),
  1196. q1 = sqrt$1(r1.x * r1.x + r1.y * r1.y + r1.z * r1.z),
  1197. q2 = sqrt$1(r2.x * r2.x + r2.y * r2.y + r2.z * r2.z);
  1198. r1.x /= q1;
  1199. r1.y /= q1;
  1200. r1.z /= q1;
  1201. r2.x /= q2;
  1202. r2.y /= q2;
  1203. r2.z /= q2;
  1204. // cross product
  1205. const c = {
  1206. x: r2.y * r1.z - r2.z * r1.y,
  1207. y: r2.z * r1.x - r2.x * r1.z,
  1208. z: r2.x * r1.y - r2.y * r1.x,
  1209. };
  1210. const m = sqrt$1(c.x * c.x + c.y * c.y + c.z * c.z);
  1211. c.x /= m;
  1212. c.y /= m;
  1213. c.z /= m;
  1214. // rotation matrix
  1215. const R = [
  1216. c.x * c.x,
  1217. c.x * c.y - c.z,
  1218. c.x * c.z + c.y,
  1219. c.x * c.y + c.z,
  1220. c.y * c.y,
  1221. c.y * c.z - c.x,
  1222. c.x * c.z - c.y,
  1223. c.y * c.z + c.x,
  1224. c.z * c.z,
  1225. ];
  1226. // normal vector:
  1227. const n = {
  1228. x: R[0] * r1.x + R[1] * r1.y + R[2] * r1.z,
  1229. y: R[3] * r1.x + R[4] * r1.y + R[5] * r1.z,
  1230. z: R[6] * r1.x + R[7] * r1.y + R[8] * r1.z,
  1231. };
  1232. return n;
  1233. }
  1234. hull(t) {
  1235. let p = this.points,
  1236. _p = [],
  1237. q = [],
  1238. idx = 0;
  1239. q[idx++] = p[0];
  1240. q[idx++] = p[1];
  1241. q[idx++] = p[2];
  1242. if (this.order === 3) {
  1243. q[idx++] = p[3];
  1244. }
  1245. // we lerp between all points at each iteration, until we have 1 point left.
  1246. while (p.length > 1) {
  1247. _p = [];
  1248. for (let i = 0, pt, l = p.length - 1; i < l; i++) {
  1249. pt = utils.lerp(t, p[i], p[i + 1]);
  1250. q[idx++] = pt;
  1251. _p.push(pt);
  1252. }
  1253. p = _p;
  1254. }
  1255. return q;
  1256. }
  1257. split(t1, t2) {
  1258. // shortcuts
  1259. if (t1 === 0 && !!t2) {
  1260. return this.split(t2).left;
  1261. }
  1262. if (t2 === 1) {
  1263. return this.split(t1).right;
  1264. }
  1265. // no shortcut: use "de Casteljau" iteration.
  1266. const q = this.hull(t1);
  1267. const result = {
  1268. left:
  1269. this.order === 2
  1270. ? new Bezier([q[0], q[3], q[5]])
  1271. : new Bezier([q[0], q[4], q[7], q[9]]),
  1272. right:
  1273. this.order === 2
  1274. ? new Bezier([q[5], q[4], q[2]])
  1275. : new Bezier([q[9], q[8], q[6], q[3]]),
  1276. span: q,
  1277. };
  1278. // make sure we bind _t1/_t2 information!
  1279. result.left._t1 = utils.map(0, 0, 1, this._t1, this._t2);
  1280. result.left._t2 = utils.map(t1, 0, 1, this._t1, this._t2);
  1281. result.right._t1 = utils.map(t1, 0, 1, this._t1, this._t2);
  1282. result.right._t2 = utils.map(1, 0, 1, this._t1, this._t2);
  1283. // if we have no t2, we're done
  1284. if (!t2) {
  1285. return result;
  1286. }
  1287. // if we have a t2, split again:
  1288. t2 = utils.map(t2, t1, 1, 0, 1);
  1289. return result.right.split(t2).left;
  1290. }
  1291. extrema() {
  1292. const result = {};
  1293. let roots = [];
  1294. this.dims.forEach(
  1295. function (dim) {
  1296. let mfn = function (v) {
  1297. return v[dim];
  1298. };
  1299. let p = this.dpoints[0].map(mfn);
  1300. result[dim] = utils.droots(p);
  1301. if (this.order === 3) {
  1302. p = this.dpoints[1].map(mfn);
  1303. result[dim] = result[dim].concat(utils.droots(p));
  1304. }
  1305. result[dim] = result[dim].filter(function (t) {
  1306. return t >= 0 && t <= 1;
  1307. });
  1308. roots = roots.concat(result[dim].sort(utils.numberSort));
  1309. }.bind(this)
  1310. );
  1311. result.values = roots.sort(utils.numberSort).filter(function (v, idx) {
  1312. return roots.indexOf(v) === idx;
  1313. });
  1314. return result;
  1315. }
  1316. bbox() {
  1317. const extrema = this.extrema(),
  1318. result = {};
  1319. this.dims.forEach(
  1320. function (d) {
  1321. result[d] = utils.getminmax(this, d, extrema[d]);
  1322. }.bind(this)
  1323. );
  1324. return result;
  1325. }
  1326. overlaps(curve) {
  1327. const lbbox = this.bbox(),
  1328. tbbox = curve.bbox();
  1329. return utils.bboxoverlap(lbbox, tbbox);
  1330. }
  1331. offset(t, d) {
  1332. if (typeof d !== "undefined") {
  1333. const c = this.get(t),
  1334. n = this.normal(t);
  1335. const ret = {
  1336. c: c,
  1337. n: n,
  1338. x: c.x + n.x * d,
  1339. y: c.y + n.y * d,
  1340. };
  1341. if (this._3d) {
  1342. ret.z = c.z + n.z * d;
  1343. }
  1344. return ret;
  1345. }
  1346. if (this._linear) {
  1347. const nv = this.normal(0),
  1348. coords = this.points.map(function (p) {
  1349. const ret = {
  1350. x: p.x + t * nv.x,
  1351. y: p.y + t * nv.y,
  1352. };
  1353. if (p.z && nv.z) {
  1354. ret.z = p.z + t * nv.z;
  1355. }
  1356. return ret;
  1357. });
  1358. return [new Bezier(coords)];
  1359. }
  1360. return this.reduce().map(function (s) {
  1361. if (s._linear) {
  1362. return s.offset(t)[0];
  1363. }
  1364. return s.scale(t);
  1365. });
  1366. }
  1367. simple() {
  1368. if (this.order === 3) {
  1369. const a1 = utils.angle(this.points[0], this.points[3], this.points[1]);
  1370. const a2 = utils.angle(this.points[0], this.points[3], this.points[2]);
  1371. if ((a1 > 0 && a2 < 0) || (a1 < 0 && a2 > 0)) return false;
  1372. }
  1373. const n1 = this.normal(0);
  1374. const n2 = this.normal(1);
  1375. let s = n1.x * n2.x + n1.y * n2.y;
  1376. if (this._3d) {
  1377. s += n1.z * n2.z;
  1378. }
  1379. return abs$1(acos$1(s)) < pi$1 / 3;
  1380. }
  1381. reduce() {
  1382. // TODO: examine these var types in more detail...
  1383. let i,
  1384. t1 = 0,
  1385. t2 = 0,
  1386. step = 0.01,
  1387. segment,
  1388. pass1 = [],
  1389. pass2 = [];
  1390. // first pass: split on extrema
  1391. let extrema = this.extrema().values;
  1392. if (extrema.indexOf(0) === -1) {
  1393. extrema = [0].concat(extrema);
  1394. }
  1395. if (extrema.indexOf(1) === -1) {
  1396. extrema.push(1);
  1397. }
  1398. for (t1 = extrema[0], i = 1; i < extrema.length; i++) {
  1399. t2 = extrema[i];
  1400. segment = this.split(t1, t2);
  1401. segment._t1 = t1;
  1402. segment._t2 = t2;
  1403. pass1.push(segment);
  1404. t1 = t2;
  1405. }
  1406. // second pass: further reduce these segments to simple segments
  1407. pass1.forEach(function (p1) {
  1408. t1 = 0;
  1409. t2 = 0;
  1410. while (t2 <= 1) {
  1411. for (t2 = t1 + step; t2 <= 1 + step; t2 += step) {
  1412. segment = p1.split(t1, t2);
  1413. if (!segment.simple()) {
  1414. t2 -= step;
  1415. if (abs$1(t1 - t2) < step) {
  1416. // we can never form a reduction
  1417. return [];
  1418. }
  1419. segment = p1.split(t1, t2);
  1420. segment._t1 = utils.map(t1, 0, 1, p1._t1, p1._t2);
  1421. segment._t2 = utils.map(t2, 0, 1, p1._t1, p1._t2);
  1422. pass2.push(segment);
  1423. t1 = t2;
  1424. break;
  1425. }
  1426. }
  1427. }
  1428. if (t1 < 1) {
  1429. segment = p1.split(t1, 1);
  1430. segment._t1 = utils.map(t1, 0, 1, p1._t1, p1._t2);
  1431. segment._t2 = p1._t2;
  1432. pass2.push(segment);
  1433. }
  1434. });
  1435. return pass2;
  1436. }
  1437. scale(d) {
  1438. const order = this.order;
  1439. let distanceFn = false;
  1440. if (typeof d === "function") {
  1441. distanceFn = d;
  1442. }
  1443. if (distanceFn && order === 2) {
  1444. return this.raise().scale(distanceFn);
  1445. }
  1446. // TODO: add special handling for degenerate (=linear) curves.
  1447. const clockwise = this.clockwise;
  1448. const r1 = distanceFn ? distanceFn(0) : d;
  1449. const r2 = distanceFn ? distanceFn(1) : d;
  1450. const v = [this.offset(0, 10), this.offset(1, 10)];
  1451. const points = this.points;
  1452. const np = [];
  1453. const o = utils.lli4(v[0], v[0].c, v[1], v[1].c);
  1454. if (!o) {
  1455. throw new Error("cannot scale this curve. Try reducing it first.");
  1456. }
  1457. // move all points by distance 'd' wrt the origin 'o'
  1458. // move end points by fixed distance along normal.
  1459. [0, 1].forEach(function (t) {
  1460. const p = (np[t * order] = utils.copy(points[t * order]));
  1461. p.x += (t ? r2 : r1) * v[t].n.x;
  1462. p.y += (t ? r2 : r1) * v[t].n.y;
  1463. });
  1464. if (!distanceFn) {
  1465. // move control points to lie on the intersection of the offset
  1466. // derivative vector, and the origin-through-control vector
  1467. [0, 1].forEach((t) => {
  1468. if (order === 2 && !!t) return;
  1469. const p = np[t * order];
  1470. const d = this.derivative(t);
  1471. const p2 = { x: p.x + d.x, y: p.y + d.y };
  1472. np[t + 1] = utils.lli4(p, p2, o, points[t + 1]);
  1473. });
  1474. return new Bezier(np);
  1475. }
  1476. // move control points by "however much necessary to
  1477. // ensure the correct tangent to endpoint".
  1478. [0, 1].forEach(function (t) {
  1479. if (order === 2 && !!t) return;
  1480. var p = points[t + 1];
  1481. var ov = {
  1482. x: p.x - o.x,
  1483. y: p.y - o.y,
  1484. };
  1485. var rc = distanceFn ? distanceFn((t + 1) / order) : d;
  1486. if (distanceFn && !clockwise) rc = -rc;
  1487. var m = sqrt$1(ov.x * ov.x + ov.y * ov.y);
  1488. ov.x /= m;
  1489. ov.y /= m;
  1490. np[t + 1] = {
  1491. x: p.x + rc * ov.x,
  1492. y: p.y + rc * ov.y,
  1493. };
  1494. });
  1495. return new Bezier(np);
  1496. }
  1497. outline(d1, d2, d3, d4) {
  1498. d2 = typeof d2 === "undefined" ? d1 : d2;
  1499. const reduced = this.reduce(),
  1500. len = reduced.length,
  1501. fcurves = [];
  1502. let bcurves = [],
  1503. p,
  1504. alen = 0,
  1505. tlen = this.length();
  1506. const graduated = typeof d3 !== "undefined" && typeof d4 !== "undefined";
  1507. function linearDistanceFunction(s, e, tlen, alen, slen) {
  1508. return function (v) {
  1509. const f1 = alen / tlen,
  1510. f2 = (alen + slen) / tlen,
  1511. d = e - s;
  1512. return utils.map(v, 0, 1, s + f1 * d, s + f2 * d);
  1513. };
  1514. }
  1515. // form curve oulines
  1516. reduced.forEach(function (segment) {
  1517. const slen = segment.length();
  1518. if (graduated) {
  1519. fcurves.push(
  1520. segment.scale(linearDistanceFunction(d1, d3, tlen, alen, slen))
  1521. );
  1522. bcurves.push(
  1523. segment.scale(linearDistanceFunction(-d2, -d4, tlen, alen, slen))
  1524. );
  1525. } else {
  1526. fcurves.push(segment.scale(d1));
  1527. bcurves.push(segment.scale(-d2));
  1528. }
  1529. alen += slen;
  1530. });
  1531. // reverse the "return" outline
  1532. bcurves = bcurves
  1533. .map(function (s) {
  1534. p = s.points;
  1535. if (p[3]) {
  1536. s.points = [p[3], p[2], p[1], p[0]];
  1537. } else {
  1538. s.points = [p[2], p[1], p[0]];
  1539. }
  1540. return s;
  1541. })
  1542. .reverse();
  1543. // form the endcaps as lines
  1544. const fs = fcurves[0].points[0],
  1545. fe = fcurves[len - 1].points[fcurves[len - 1].points.length - 1],
  1546. bs = bcurves[len - 1].points[bcurves[len - 1].points.length - 1],
  1547. be = bcurves[0].points[0],
  1548. ls = utils.makeline(bs, fs),
  1549. le = utils.makeline(fe, be),
  1550. segments = [ls].concat(fcurves).concat([le]).concat(bcurves);
  1551. return new PolyBezier(segments);
  1552. }
  1553. outlineshapes(d1, d2, curveIntersectionThreshold) {
  1554. d2 = d2 || d1;
  1555. const outline = this.outline(d1, d2).curves;
  1556. const shapes = [];
  1557. for (let i = 1, len = outline.length; i < len / 2; i++) {
  1558. const shape = utils.makeshape(
  1559. outline[i],
  1560. outline[len - i],
  1561. curveIntersectionThreshold
  1562. );
  1563. shape.startcap.virtual = i > 1;
  1564. shape.endcap.virtual = i < len / 2 - 1;
  1565. shapes.push(shape);
  1566. }
  1567. return shapes;
  1568. }
  1569. intersects(curve, curveIntersectionThreshold) {
  1570. if (!curve) return this.selfintersects(curveIntersectionThreshold);
  1571. if (curve.p1 && curve.p2) {
  1572. return this.lineIntersects(curve);
  1573. }
  1574. if (curve instanceof Bezier) {
  1575. curve = curve.reduce();
  1576. }
  1577. return this.curveintersects(
  1578. this.reduce(),
  1579. curve,
  1580. curveIntersectionThreshold
  1581. );
  1582. }
  1583. lineIntersects(line) {
  1584. const mx = min(line.p1.x, line.p2.x),
  1585. my = min(line.p1.y, line.p2.y),
  1586. MX = max(line.p1.x, line.p2.x),
  1587. MY = max(line.p1.y, line.p2.y);
  1588. return utils.roots(this.points, line).filter((t) => {
  1589. var p = this.get(t);
  1590. return utils.between(p.x, mx, MX) && utils.between(p.y, my, MY);
  1591. });
  1592. }
  1593. selfintersects(curveIntersectionThreshold) {
  1594. // "simple" curves cannot intersect with their direct
  1595. // neighbour, so for each segment X we check whether
  1596. // it intersects [0:x-2][x+2:last].
  1597. const reduced = this.reduce(),
  1598. len = reduced.length - 2,
  1599. results = [];
  1600. for (let i = 0, result, left, right; i < len; i++) {
  1601. left = reduced.slice(i, i + 1);
  1602. right = reduced.slice(i + 2);
  1603. result = this.curveintersects(left, right, curveIntersectionThreshold);
  1604. results.push(...result);
  1605. }
  1606. return results;
  1607. }
  1608. curveintersects(c1, c2, curveIntersectionThreshold) {
  1609. const pairs = [];
  1610. // step 1: pair off any overlapping segments
  1611. c1.forEach(function (l) {
  1612. c2.forEach(function (r) {
  1613. if (l.overlaps(r)) {
  1614. pairs.push({ left: l, right: r });
  1615. }
  1616. });
  1617. });
  1618. // step 2: for each pairing, run through the convergence algorithm.
  1619. let intersections = [];
  1620. pairs.forEach(function (pair) {
  1621. const result = utils.pairiteration(
  1622. pair.left,
  1623. pair.right,
  1624. curveIntersectionThreshold
  1625. );
  1626. if (result.length > 0) {
  1627. intersections = intersections.concat(result);
  1628. }
  1629. });
  1630. return intersections;
  1631. }
  1632. arcs(errorThreshold) {
  1633. errorThreshold = errorThreshold || 0.5;
  1634. return this._iterate(errorThreshold, []);
  1635. }
  1636. _error(pc, np1, s, e) {
  1637. const q = (e - s) / 4,
  1638. c1 = this.get(s + q),
  1639. c2 = this.get(e - q),
  1640. ref = utils.dist(pc, np1),
  1641. d1 = utils.dist(pc, c1),
  1642. d2 = utils.dist(pc, c2);
  1643. return abs$1(d1 - ref) + abs$1(d2 - ref);
  1644. }
  1645. _iterate(errorThreshold, circles) {
  1646. let t_s = 0,
  1647. t_e = 1,
  1648. safety;
  1649. // we do a binary search to find the "good `t` closest to no-longer-good"
  1650. do {
  1651. safety = 0;
  1652. // step 1: start with the maximum possible arc
  1653. t_e = 1;
  1654. // points:
  1655. let np1 = this.get(t_s),
  1656. np2,
  1657. np3,
  1658. arc,
  1659. prev_arc;
  1660. // booleans:
  1661. let curr_good = false,
  1662. prev_good = false,
  1663. done;
  1664. // numbers:
  1665. let t_m = t_e,
  1666. prev_e = 1;
  1667. // step 2: find the best possible arc
  1668. do {
  1669. prev_good = curr_good;
  1670. prev_arc = arc;
  1671. t_m = (t_s + t_e) / 2;
  1672. np2 = this.get(t_m);
  1673. np3 = this.get(t_e);
  1674. arc = utils.getccenter(np1, np2, np3);
  1675. //also save the t values
  1676. arc.interval = {
  1677. start: t_s,
  1678. end: t_e,
  1679. };
  1680. let error = this._error(arc, np1, t_s, t_e);
  1681. curr_good = error <= errorThreshold;
  1682. done = prev_good && !curr_good;
  1683. if (!done) prev_e = t_e;
  1684. // this arc is fine: we can move 'e' up to see if we can find a wider arc
  1685. if (curr_good) {
  1686. // if e is already at max, then we're done for this arc.
  1687. if (t_e >= 1) {
  1688. // make sure we cap at t=1
  1689. arc.interval.end = prev_e = 1;
  1690. prev_arc = arc;
  1691. // if we capped the arc segment to t=1 we also need to make sure that
  1692. // the arc's end angle is correct with respect to the bezier end point.
  1693. if (t_e > 1) {
  1694. let d = {
  1695. x: arc.x + arc.r * cos$1(arc.e),
  1696. y: arc.y + arc.r * sin$1(arc.e),
  1697. };
  1698. arc.e += utils.angle({ x: arc.x, y: arc.y }, d, this.get(1));
  1699. }
  1700. break;
  1701. }
  1702. // if not, move it up by half the iteration distance
  1703. t_e = t_e + (t_e - t_s) / 2;
  1704. } else {
  1705. // this is a bad arc: we need to move 'e' down to find a good arc
  1706. t_e = t_m;
  1707. }
  1708. } while (!done && safety++ < 100);
  1709. if (safety >= 100) {
  1710. break;
  1711. }
  1712. // console.log("L835: [F] arc found", t_s, prev_e, prev_arc.x, prev_arc.y, prev_arc.s, prev_arc.e);
  1713. prev_arc = prev_arc ? prev_arc : arc;
  1714. circles.push(prev_arc);
  1715. t_s = prev_e;
  1716. } while (t_e < 1);
  1717. return circles;
  1718. }
  1719. }
  1720. export { Bezier };