1 /*
  2     Copyright 2008-2024
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
 29     and <https://opensource.org/licenses/MIT/>.
 30 
 31 
 32     Metapost/Hobby curves, see e.g. https://bosker.wordpress.com/2013/11/13/beyond-bezier-curves/
 33 
 34     * Ported to Python for the project PyX. Copyright (C) 2011 Michael Schindler <m-schindler@users.sourceforge.net>
 35     * Ported to javascript from the PyX implementation (https://pyx-project.org/) by Vlad-X.
 36     * Adapted to JSXGraph and some code changes by Alfred Wassermann 2020.
 37 
 38     This program is distributed in the hope that it will be useful,
 39     but WITHOUT ANY WARRANTY; without even the implied warranty of
 40     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 41     GNU General Public License for more details.
 42 
 43     You should have received a copy of the GNU General Public License
 44     along with this program; if not, write to the Free Software
 45     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 46 
 47     Internal functions of MetaPost
 48     This file re-implements some of the functionality of MetaPost
 49     (https://tug.org/metapost.html). MetaPost was developed by John D. Hobby and
 50     others. The code of Metapost is in the public domain, which we understand as
 51     an implicit permission to reuse the code here (see the comment at
 52     https://www.gnu.org/licenses/license-list.html)
 53 
 54     This file is based on the MetaPost version distributed by TeXLive:
 55     svn://tug.org/texlive/trunk/Build/source/texk/web2c/mplibdir revision 22737 #
 56     (2011-05-31)
 57 */
 58 
 59 /*global JXG: true, define: true*/
 60 /*jslint nomen: true, plusplus: true*/
 61 
 62 /**
 63  * @fileoverview In this file the namespace Math.Metapost is defined which holds algorithms translated from Metapost
 64  * by D.E. Knuth and J.D. Hobby.
 65  */
 66 
 67 import Type from "../utils/type.js";
 68 import Mat from "./math.js";
 69 
 70 /**
 71  * The JXG.Math.Metapost namespace holds algorithms translated from Metapost
 72  * by D.E. Knuth and J.D. Hobby.
 73  *
 74  * @name JXG.Math.Metapost
 75  * @exports Mat.Metapost as JXG.Math.Metapost
 76  * @namespace
 77  */
 78 Mat.Metapost = {
 79     MP_ENDPOINT: 0,
 80     MP_EXPLICIT: 1,
 81     MP_GIVEN: 2,
 82     MP_CURL: 3,
 83     MP_OPEN: 4,
 84     MP_END_CYCLE: 5,
 85 
 86     UNITY: 1.0,
 87     // two: 2,
 88     // fraction_half: 0.5,
 89     FRACTION_ONE: 1.0,
 90     FRACTION_THREE: 3.0,
 91     ONE_EIGHTY_DEG: Math.PI,
 92     THREE_SIXTY_DEG: 2 * Math.PI,
 93     // EPSILON: 1e-5,
 94     EPS_SQ: 1e-5 * 1e-5,
 95 
 96     /**
 97      * @private
 98      */
 99     make_choices: function (knots) {
100         var dely, h, k, delx, n, q, p, s, cosine, t, sine, delta_x, delta_y, delta, psi,
101             endless = true;
102 
103         p = knots[0];
104         do {
105             if (!p) {
106                 break;
107             }
108             q = p.next;
109 
110             // Join two identical knots by setting the control points to the same
111             // coordinates.
112             // MP 291
113             if (
114                 p.rtype > this.MP_EXPLICIT &&
115                 (p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y) < this.EPS_SQ
116             ) {
117                 p.rtype = this.MP_EXPLICIT;
118                 if (p.ltype === this.MP_OPEN) {
119                     p.ltype = this.MP_CURL;
120                     p.set_left_curl(this.UNITY);
121                 }
122 
123                 q.ltype = this.MP_EXPLICIT;
124                 if (q.rtype === this.MP_OPEN) {
125                     q.rtype = this.MP_CURL;
126                     q.set_right_curl(this.UNITY);
127                 }
128 
129                 p.rx = p.x;
130                 q.lx = p.x;
131                 p.ry = p.y;
132                 q.ly = p.y;
133             }
134             p = q;
135         } while (p !== knots[0]);
136 
137         // Find the first breakpoint, h, on the path
138         // MP 292
139         h = knots[0];
140         while (endless) {
141             if (h.ltype !== this.MP_OPEN || h.rtype !== this.MP_OPEN) {
142                 break;
143             }
144             h = h.next;
145             if (h === knots[0]) {
146                 h.ltype = this.MP_END_CYCLE;
147                 break;
148             }
149         }
150 
151         p = h;
152         while (endless) {
153             if (!p) {
154                 break;
155             }
156 
157             // Fill in the control points between p and the next breakpoint,
158             // then advance p to that breakpoint
159             // MP 299
160             q = p.next;
161             if (p.rtype >= this.MP_GIVEN) {
162                 while (q.ltype === this.MP_OPEN && q.rtype === this.MP_OPEN) {
163                     q = q.next;
164                 }
165 
166                 // Calculate the turning angles psi_ k and the distances d_{k,k+1};
167                 // set n to the length of the path
168                 // MP 302
169                 k = 0;
170                 s = p;
171                 n = knots.length;
172 
173                 delta_x = [];
174                 delta_y = [];
175                 delta = [];
176                 psi = [null];
177 
178                 // tuple([]) = tuple([[], [], [], [null]]);
179                 while (endless) {
180                     t = s.next;
181                     // None;
182                     delta_x.push(t.x - s.x);
183                     delta_y.push(t.y - s.y);
184                     delta.push(this.mp_pyth_add(delta_x[k], delta_y[k]));
185                     if (k > 0) {
186                         sine = delta_y[k - 1] / delta[k - 1];
187                         cosine = delta_x[k - 1] / delta[k - 1];
188                         psi.push(
189                             Math.atan2(
190                                 delta_y[k] * cosine - delta_x[k] * sine,
191                                 delta_x[k] * cosine + delta_y[k] * sine
192                             )
193                         );
194                     }
195                     k++;
196                     s = t;
197                     if (s === q) {
198                         n = k;
199                     }
200                     if (k >= n && s.ltype !== this.MP_END_CYCLE) {
201                         break;
202                     }
203                 }
204                 if (k === n) {
205                     psi.push(0);
206                 } else {
207                     psi.push(psi[1]);
208                 }
209 
210                 // Remove open types at the breakpoints
211                 // MP 303
212                 if (q.ltype === this.MP_OPEN) {
213                     delx = q.rx - q.x;
214                     dely = q.ry - q.y;
215                     if (delx * delx + dely * dely < this.EPS_SQ) {
216                         q.ltype = this.MP_CURL;
217                         q.set_left_curl(this.UNITY);
218                     } else {
219                         q.ltype = this.MP_GIVEN;
220                         q.set_left_given(Math.atan2(dely, delx));
221                     }
222                 }
223                 if (p.rtype === this.MP_OPEN && p.ltype === this.MP_EXPLICIT) {
224                     delx = p.x - p.lx;
225                     dely = p.y - p.ly;
226                     if (delx * delx + dely * dely < this.EPS_SQ) {
227                         p.rtype = this.MP_CURL;
228                         p.set_right_curl(this.UNITY);
229                     } else {
230                         p.rtype = this.MP_GIVEN;
231                         p.set_right_given(Math.atan2(dely, delx));
232                     }
233                 }
234                 this.mp_solve_choices(p, q, n, delta_x, delta_y, delta, psi);
235             } else if (p.rtype === this.MP_ENDPOINT) {
236                 // MP 294
237                 p.rx = p.x;
238                 p.ry = p.y;
239                 q.lx = q.x;
240                 q.ly = q.y;
241             }
242             p = q;
243 
244             if (p === h) {
245                 break;
246             }
247         }
248     },
249 
250     /**
251      * Implements solve_choices form metapost
252      * MP 305
253      * @private
254      */
255     mp_solve_choices: function (p, q, n, delta_x, delta_y, delta, psi) {
256         var aa, acc, vv, bb, ldelta, ee, k,
257             s, ww, uu, lt, r, t, ff,
258             theta, rt, dd, cc, ct_st,
259             ct, st, cf_sf, cf, sf, i, k_idx,
260             endless = true;
261 
262         ldelta = delta.length + 1;
263         uu = new Array(ldelta);
264         ww = new Array(ldelta);
265         vv = new Array(ldelta);
266         theta = new Array(ldelta);
267         for (i = 0; i < ldelta; i++) {
268             theta[i] = vv[i] = ww[i] = uu[i] = 0;
269         }
270         k = 0;
271         s = p;
272         r = 0;
273         while (endless) {
274             t = s.next;
275             if (k === 0) {
276                 // MP 306
277                 if (s.rtype === this.MP_GIVEN) {
278                     // MP 314
279                     if (t.ltype === this.MP_GIVEN) {
280                         aa = Math.atan2(delta_y[0], delta_x[0]);
281                         ct_st = this.mp_n_sin_cos(p.right_given() - aa);
282                         ct = ct_st[0];
283                         st = ct_st[1];
284                         cf_sf = this.mp_n_sin_cos(q.left_given() - aa);
285                         cf = cf_sf[0];
286                         sf = cf_sf[1];
287                         this.mp_set_controls(p, q, delta_x[0], delta_y[0], st, ct, -sf, cf);
288                         return;
289                     }
290                     vv[0] = s.right_given() - Math.atan2(delta_y[0], delta_x[0]);
291                     vv[0] = this.reduce_angle(vv[0]);
292                     uu[0] = 0;
293                     ww[0] = 0;
294                 } else if (s.rtype === this.MP_CURL) {
295                     // MP 315
296                     if (t.ltype === this.MP_CURL) {
297                         p.rtype = this.MP_EXPLICIT;
298                         q.ltype = this.MP_EXPLICIT;
299                         lt = Math.abs(q.left_tension());
300                         rt = Math.abs(p.right_tension());
301                         ff = this.UNITY / (3.0 * rt);
302                         p.rx = p.x + delta_x[0] * ff;
303                         p.ry = p.y + delta_y[0] * ff;
304                         ff = this.UNITY / (3.0 * lt);
305                         q.lx = q.x - delta_x[0] * ff;
306                         q.ly = q.y - delta_y[0] * ff;
307                         return;
308                     }
309                     cc = s.right_curl();
310                     lt = Math.abs(t.left_tension());
311                     rt = Math.abs(s.right_tension());
312                     uu[0] = this.mp_curl_ratio(cc, rt, lt);
313                     vv[0] = -psi[1] * uu[0];
314                     ww[0] = 0;
315                 } else {
316                     if (s.rtype === this.MP_OPEN) {
317                         uu[0] = 0;
318                         vv[0] = 0;
319                         ww[0] = this.FRACTION_ONE;
320                     }
321                 }
322             } else {
323                 if (s.ltype === this.MP_END_CYCLE || s.ltype === this.MP_OPEN) {
324                     // MP 308
325                     aa = this.UNITY / (3.0 * Math.abs(r.right_tension()) - this.UNITY);
326                     dd =
327                         delta[k] *
328                         (this.FRACTION_THREE - this.UNITY / Math.abs(r.right_tension()));
329                     bb = this.UNITY / (3 * Math.abs(t.left_tension()) - this.UNITY);
330                     ee =
331                         delta[k - 1] *
332                         (this.FRACTION_THREE - this.UNITY / Math.abs(t.left_tension()));
333                     cc = this.FRACTION_ONE - uu[k - 1] * aa;
334                     dd = dd * cc;
335                     lt = Math.abs(s.left_tension());
336                     rt = Math.abs(s.right_tension());
337                     if (lt < rt) {
338                         dd *= Math.pow(lt / rt, 2);
339                     } else {
340                         if (lt > rt) {
341                             ee *= Math.pow(rt / lt, 2);
342                         }
343                     }
344                     ff = ee / (ee + dd);
345                     uu[k] = ff * bb;
346                     acc = -psi[k + 1] * uu[k];
347                     if (r.rtype === this.MP_CURL) {
348                         ww[k] = 0;
349                         vv[k] = acc - psi[1] * (this.FRACTION_ONE - ff);
350                     } else {
351                         ff = (this.FRACTION_ONE - ff) / cc;
352                         acc = acc - psi[k] * ff;
353                         ff = ff * aa;
354                         vv[k] = acc - vv[k - 1] * ff;
355                         ww[k] = -ww[k - 1] * ff;
356                     }
357                     if (s.ltype === this.MP_END_CYCLE) {
358                         aa = 0;
359                         bb = this.FRACTION_ONE;
360                         while (endless) {
361                             k -= 1;
362                             if (k === 0) {
363                                 k = n;
364                             }
365                             aa = vv[k] - aa * uu[k];
366                             bb = ww[k] - bb * uu[k];
367                             if (k === n) {
368                                 break;
369                             }
370                         }
371                         aa = aa / (this.FRACTION_ONE - bb);
372                         theta[n] = aa;
373                         vv[0] = aa;
374                         // k_val = range(1, n);
375                         // for (k_idx in k_val) {
376                         // k = k_val[k_idx];
377                         for (k_idx = 1; k_idx < n; k_idx++) {
378                             vv[k_idx] = vv[k_idx] + aa * ww[k_idx];
379                         }
380                         break;
381                     }
382                 } else {
383                     if (s.ltype === this.MP_CURL) {
384                         cc = s.left_curl();
385                         lt = Math.abs(s.left_tension());
386                         rt = Math.abs(r.right_tension());
387                         ff = this.mp_curl_ratio(cc, lt, rt);
388                         theta[n] = -(vv[n - 1] * ff) / (this.FRACTION_ONE - ff * uu[n - 1]);
389                         break;
390                     }
391                     if (s.ltype === this.MP_GIVEN) {
392                         theta[n] = s.left_given() - Math.atan2(delta_y[n - 1], delta_x[n - 1]);
393                         theta[n] = this.reduce_angle(theta[n]);
394                         break;
395                     }
396                 }
397             }
398             r = s;
399             s = t;
400             k += 1;
401         }
402 
403         // MP 318
404         for (k = n - 1; k > -1; k--) {
405             theta[k] = vv[k] - theta[k + 1] * uu[k];
406         }
407 
408         s = p;
409         k = 0;
410         while (endless) {
411             t = s.next;
412             ct_st = this.mp_n_sin_cos(theta[k]);
413             ct = ct_st[0];
414             st = ct_st[1];
415             cf_sf = this.mp_n_sin_cos(-psi[k + 1] - theta[k + 1]);
416             cf = cf_sf[0];
417             sf = cf_sf[1];
418             this.mp_set_controls(s, t, delta_x[k], delta_y[k], st, ct, sf, cf);
419             k++;
420             s = t;
421             if (k === n) {
422                 break;
423             }
424         }
425     },
426 
427     /**
428      * @private
429      */
430     mp_n_sin_cos: function (z) {
431         return [Math.cos(z), Math.sin(z)];
432     },
433 
434     /**
435      * @private
436      */
437     mp_set_controls: function (p, q, delta_x, delta_y, st, ct, sf, cf) {
438         var rt, ss, lt, sine, rr;
439         lt = Math.abs(q.left_tension());
440         rt = Math.abs(p.right_tension());
441         rr = this.mp_velocity(st, ct, sf, cf, rt);
442         ss = this.mp_velocity(sf, cf, st, ct, lt);
443 
444         // console.log('lt rt rr ss', lt, rt, rr, ss);
445         if (p.right_tension() < 0 || q.left_tension() < 0) {
446             if ((st >= 0 && sf >= 0) || (st <= 0 && sf <= 0)) {
447                 sine = Math.abs(st) * cf + Math.abs(sf) * ct;
448                 if (sine > 0) {
449                     sine *= 1.00024414062;
450                     if (p.right_tension() < 0) {
451                         if (this.mp_ab_vs_cd(Math.abs(sf), this.FRACTION_ONE, rr, sine) < 0) {
452                             rr = Math.abs(sf) / sine;
453                         }
454                     }
455                     if (q.left_tension() < 0) {
456                         if (this.mp_ab_vs_cd(Math.abs(st), this.FRACTION_ONE, ss, sine) < 0) {
457                             ss = Math.abs(st) / sine;
458                         }
459                     }
460                 }
461             }
462         }
463         p.rx = p.x + (delta_x * ct - delta_y * st) * rr;
464         p.ry = p.y + (delta_y * ct + delta_x * st) * rr;
465         q.lx = q.x - (delta_x * cf + delta_y * sf) * ss;
466         q.ly = q.y - (delta_y * cf - delta_x * sf) * ss;
467         p.rtype = this.MP_EXPLICIT;
468         q.ltype = this.MP_EXPLICIT;
469     },
470 
471     /**
472      * @private
473      */
474     mp_pyth_add: function (a, b) {
475         return Mat.hypot(a, b);
476     },
477 
478     /**
479      *
480      * @private
481      */
482     mp_curl_ratio: function (gamma, a_tension, b_tension) {
483         var alpha = 1.0 / a_tension,
484             beta = 1.0 / b_tension;
485 
486         return Math.min(
487             4.0,
488             ((3.0 - alpha) * alpha * alpha * gamma + beta * beta * beta) /
489             (alpha * alpha * alpha * gamma + (3.0 - beta) * beta * beta)
490         );
491     },
492 
493     /**
494      * @private
495      */
496     mp_ab_vs_cd: function (a, b, c, d) {
497         if (a * b === c * d) {
498             return 0;
499         }
500         if (a * b > c * d) {
501             return 1;
502         }
503         return -1;
504     },
505 
506     /**
507      * @private
508      */
509     mp_velocity: function (st, ct, sf, cf, t) {
510         return Math.min(
511             4.0,
512             (2.0 + Math.sqrt(2) * (st - sf / 16.0) * (sf - st / 16.0) * (ct - cf)) /
513             (1.5 * t * (2 + (Math.sqrt(5) - 1) * ct + (3 - Math.sqrt(5)) * cf))
514         );
515     },
516 
517     /**
518      * @private
519      * @param {Number} A
520      */
521     reduce_angle: function (A) {
522         if (Math.abs(A) > this.ONE_EIGHTY_DEG) {
523             if (A > 0) {
524                 A -= this.THREE_SIXTY_DEG;
525             } else {
526                 A += this.THREE_SIXTY_DEG;
527             }
528         }
529         return A;
530     },
531 
532     /**
533      *
534      * @private
535      * @param {Array} p
536      * @param {Number} tension
537      * @param {Boolean} cycle
538      */
539     makeknots: function (p, tension) {
540         var i, len,
541             knots = [];
542 
543         len = p.length;
544         for (i = 0; i < len; i++) {
545             knots.push({
546                 x: p[i][0],
547                 y: p[i][1],
548                 ltype: this.MP_OPEN,
549                 rtype: this.MP_OPEN,
550                 lx: false,
551                 rx: false,
552                 ly: tension,
553                 ry: tension,
554                 left_curl: function () {
555                     return this.lx || 0;
556                 },
557                 right_curl: function () {
558                     return this.rx || 0;
559                 },
560                 left_tension: function () {
561                     return this.ly || 1;
562                 },
563                 right_tension: function () {
564                     return this.ry || 1;
565                 },
566                 set_right_curl: function (v) {
567                     this.rx = v || 0;
568                 },
569                 set_left_curl: function (v) {
570                     this.lx = v || 0;
571                 }
572             });
573         }
574 
575         len = knots.length;
576         for (i = 0; i < len; i++) {
577             knots[i].next = knots[i + 1] || knots[i];
578             knots[i].set_right_given = knots[i].set_right_curl;
579             knots[i].set_left_given = knots[i].set_left_curl;
580             knots[i].right_given = knots[i].right_curl;
581             knots[i].left_given = knots[i].left_curl;
582         }
583         knots[len - 1].next = knots[0];
584 
585         return knots;
586     },
587 
588     /**
589      *
590      * @param {Array} point_list
591      * @param {Object} controls
592      *
593      * @returns {Array}
594      */
595     curve: function (point_list, controls) {
596         var knots, len, i, ii,
597             val, obj,
598             isClosed = false,
599             x = [],
600             y = [];
601 
602         controls = controls || {
603             tension: 1,
604             direction: {},
605             curl: {},
606             isClosed: false
607         };
608 
609         // Change default tension
610         val = 1;
611         if (controls.hasOwnProperty('tension')) {
612             val = Type.evaluate(controls.tension);
613         }
614 
615         knots = this.makeknots(point_list, val);
616 
617         len = knots.length;
618         if (Type.exists(controls.isClosed) && Type.evaluate(controls.isClosed)) {
619             isClosed = true;
620         }
621 
622         if (!isClosed) {
623             knots[0].ltype = this.MP_ENDPOINT;
624             knots[0].rtype = this.MP_CURL;
625             knots[len - 1].rtype = this.MP_ENDPOINT;
626             knots[len - 1].ltype = this.MP_CURL;
627         }
628 
629         // for (i in controls.direction) {
630         //     if (controls.direction.hasOwnProperty(i)) {
631         //         val = Type.evaluate(controls.direction[i]);
632         //         if (Type.isArray(val)) {
633         //             if (val[0] !== false) {
634         //                 knots[i].lx = (val[0] * Math.PI) / 180;
635         //                 knots[i].ltype = this.MP_GIVEN;
636         //             }
637         //             if (val[1] !== false) {
638         //                 knots[i].rx = (val[1] * Math.PI) / 180;
639         //                 knots[i].rtype = this.MP_GIVEN;
640         //             }
641         //         } else {
642         //             knots[i].lx = (val * Math.PI) / 180;
643         //             knots[i].rx = (val * Math.PI) / 180;
644         //             knots[i].ltype = knots[i].rtype = this.MP_GIVEN;
645         //         }
646         //     }
647         // }
648 
649         // for (i in controls.curl) {
650         //     if (controls.curl.hasOwnProperty(i)) {
651         //         val = Type.evaluate(controls.curl[i]);
652         //         if (parseInt(i, 10) === 0) {
653         //             knots[i].rtype = this.MP_CURL;
654         //             knots[i].set_right_curl(val);
655         //         } else if (parseInt(i, 10) === len - 1) {
656         //             knots[i].ltype = this.MP_CURL;
657         //             knots[i].set_left_curl(val);
658         //         }
659         //     }
660         // }
661 
662         // Set individual point control values
663         for (ii in controls) {
664             if (controls.hasOwnProperty(ii)) {
665                 i = parseInt(ii, 10);
666                 if (isNaN(i) || i < 0 || i >= len) {
667                     continue;
668                 }
669 
670                 // Handle individual curl
671                 obj = controls[i];
672                 if (Type.exists(obj.type)) {
673                     switch (obj.type) {
674                         case 'curl':
675                             val = Type.evaluate(obj.curl);
676                             if (i === 0) {
677                                 knots[i].rtype = this.MP_CURL;
678                                 knots[i].set_right_curl(val);
679                             } else if (i === len - 1) {
680                                 knots[i].ltype = this.MP_CURL;
681                                 knots[i].set_left_curl(val);
682                             } else {
683                                 knots[i].ltype = this.MP_CURL;
684                                 knots[i].rtype = this.MP_CURL;
685                                 knots[i].lx = val;
686                                 knots[i].rx = val;
687                             }
688                             break;
689                         }
690                     }
691 
692                     // Handle individual directions
693                     if (Type.exists(obj.direction)) {
694                         val = Type.evaluate(obj.direction);
695                         if (Type.isArray(val)) {
696                             if (val[0] !== false) {
697                                 knots[i].lx = (val[0] * Math.PI) / 180;
698                                 knots[i].ltype = this.MP_GIVEN;
699                             }
700                             if (val[1] !== false) {
701                                 knots[i].rx = (val[1] * Math.PI) / 180;
702                                 knots[i].rtype = this.MP_GIVEN;
703                             }
704                         } else {
705                             knots[i].lx = (val * Math.PI) / 180;
706                             knots[i].rx = (val * Math.PI) / 180;
707                             knots[i].ltype = knots[i].rtype = this.MP_GIVEN;
708                         }
709                     }
710 
711                     // Handle individual tension
712                     if (Type.exists(obj.tension)) {
713                         val = Type.evaluate(obj.tension);
714                         if (Type.isArray(val)) {
715                             if (val[0] !== false) {
716                                 knots[i].ly = Type.evaluate(val[0]);
717                             }
718                             if (val[1] !== false) {
719                                 knots[i].ry = Type.evaluate(val[1]);
720                             }
721                         } else {
722                             knots[i].ly = val;
723                             knots[i].ry = val;
724                         }
725                     }
726                 }
727             }
728 
729             // Generate ths Bezier curve
730             this.make_choices(knots);
731 
732             // Return the coordinates
733             for (i = 0; i < len - 1; i++) {
734                 x.push(knots[i].x);
735                 x.push(knots[i].rx);
736                 x.push(knots[i + 1].lx);
737                 y.push(knots[i].y);
738                 y.push(knots[i].ry);
739                 y.push(knots[i + 1].ly);
740             }
741             x.push(knots[len - 1].x);
742             y.push(knots[len - 1].y);
743 
744             if (isClosed) {
745                 x.push(knots[len - 1].rx);
746                 y.push(knots[len - 1].ry);
747                 x.push(knots[0].lx);
748                 y.push(knots[0].ly);
749                 x.push(knots[0].x);
750                 y.push(knots[0].y);
751             }
752 
753             return [x, y];
754         }
755 };
756 
757 export default Mat.Metapost;
758