      1 /*
      2     Copyright 2008-2025
      3         Matthias Ehmann,
      4         Carsten Miller,
      5         Andreas Walter,
      6         Alfred Wassermann
      7
      8     This file is part of JSXGraph.
      9
     10     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
     11
     12     You can redistribute it and/or modify it under the terms of the
     13
     14       * GNU Lesser General Public License as published by
     15         the Free Software Foundation, either version 3 of the License, or
     16         (at your option) any later version
     17       OR
     18       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
     19
     20     JSXGraph is distributed in the hope that it will be useful,
     21     but WITHOUT ANY WARRANTY; without even the implied warranty of
     22     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     23     GNU Lesser General Public License for more details.
     24
     25     You should have received a copy of the GNU Lesser General Public License and
     26     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
     27     and <https://opensource.org/licenses/MIT/>.
     28  */
     29 /*global JXG:true, define: true*/
     30
     31 import JXG from "../jxg.js";
     32 import Const from "../base/constants.js";
     33 import Geometry from "../math/geometry.js";
     34 import Type from "../utils/type.js";
     35 import Mat from "../math/math.js";
     36
     37 /**
     38  * Constructor for 3D curves.
     39  * @class Creates a new 3D curve object. Do not use this constructor to create a 3D curve. Use {@link JXG.View3D#create} with type {@link Curve3D} instead.
     40  *
     41  * @augments JXG.GeometryElement3D
     42  * @augments JXG.GeometryElement
     43  * @param {View3D} view
     44  * @param {Function} F
     45  * @param {Function} X
     46  * @param {Function} Y
     47  * @param {Function} Z
     48  * @param {Array} range
     49  * @param {Object} attributes
     50  * @see JXG.Board#generateName
     51  */
     52 JXG.Curve3D = function (view, F, X, Y, Z, range, attributes) {
     53     this.constructor(view.board, attributes, Const.OBJECT_TYPE_CURVE3D, Const.OBJECT_CLASS_3D);
     54     this.constructor3D(view, "curve3d");
     55
     56     this.board.finalizeAdding(this);
     57
     58     /**
     59      * Internal function defining the surface without applying any transformations.
     60      * Does only exist if it or X are supplied as a function. Otherwise it is null.
     61      *
     62      * @function
     63      * @private
     64      */
     65     this._F = F;
     66
     67     /**
     68      * Function or array which maps u to x; i.e. it defines the x-coordinate of the curve
     69      * @function
     70      * @returns Number
     71      * @private
     72      */
     73     this._X = X;
     74
     75     /**
     76      * Function or array  which maps u to y; i.e. it defines the y-coordinate of the curve
     77      * @function
     78      * @returns Number
     79      * @private
     80      */
     81     this._Y = Y;
     82
     83     /**
     84      * Function or array  which maps u to z; i.e. it defines the z-coordinate of the curve
     85      * @function
     86      * @returns Number
     87      * @private
     88      */
     89     this._Z = Z;
     90
     91     this.points = [];
     92
     93     this.numberPoints = 0;
     94
     95     this.dataX = null;
     96     this.dataY = null;
     97     this.dataZ = null;
     98
     99     if (this._F !== null) {
    100         this._X = function (u) {
    101             return this._F(u)[0];
    102         };
    103         this._Y = function (u) {
    104             return this._F(u)[1];
    105         };
    106         this._Z = function (u) {
    107             return this._F(u)[2];
    108         };
    109     } else {
    110         if (Type.isFunction(this._X)) {
    111             this._F = function(u) {
    112                 return [this._X(u), this._Y(u), this._Z(u)];
    113             };
    114         } else {
    115             this._F = null;
    116         }
    117     }
    118
    119     this.range = range;
    120
    121     this.methodMap = Type.deepCopy(this.methodMap, {
    122         // TODO
    123     });
    124 };
    125 JXG.Curve3D.prototype = new JXG.GeometryElement();
    126 Type.copyPrototypeMethods(JXG.Curve3D, JXG.GeometryElement3D, "constructor3D");
    127
    128 JXG.extend(
    129     JXG.Curve3D.prototype,
    130     /** @lends JXG.Curve3D.prototype */ {
    131
    132         /**
    133          * Simple curve plotting algorithm.
    134          *
    135          * @returns {JXG.Curve3D} Reference to itself
    136          */
    137         updateCoords: function() {
    138             var steps = this.evalVisProp('numberpointshigh'),
    139                 r, s, e, delta,
    140                 u, i,
    141                 c3d = [1, 0, 0, 0];
    142
    143             this.points = [];
    144
    145             if (Type.exists(this.dataX)) {
    146                 steps = this.dataX.length;
    147                 for (u = 0; u < steps; u++) {
    148                     this.points.push([1, this.dataX[u], this.dataY[u], this.dataZ[u]]);
    149                 }
    150             } else if (Type.isArray(this._X)) {
    151                 steps = this._X.length;
    152                 for (u = 0; u < steps; u++) {
    153                     this.points.push([1, this._X[u], this._Y[u], this._Z[u]]);
    154                 }
    155             } else {
    156                 r = Type.evaluate(this.range);
    157                 s = Type.evaluate(r[0]);
    158                 e = Type.evaluate(r[1]);
    159                 delta = (e - s) / (steps - 1);
    160                 for (i = 0, u = s; i < steps && u <= e; i++, u += delta) {
    161                     c3d = this.F(u);
    162                     c3d.unshift(1);
    163                     this.points.push(c3d);
    164                 }
    165             }
    166             this.numberPoints = this.points.length;
    167
    168             return this;
    169         },
    170
    171         /**
    172          * Generic function which evaluates the function term of the curve
    173          * and applies its transformations.
    174          * @param {Number} u
    175          * @returns
    176          */
    177         evalF: function(u) {
    178             var t, i,
    179                 c3d = [0, 0, 0, 0];
    180
    181             if (this.transformations.length === 0 || !Type.exists(this.baseElement)) {
    182                 if (Type.exists(this._F)) {
    183                     c3d = this._F(u);
    184                 } else {
    185                     c3d = [this._X[u], this._Y[u], this._Z[u]];
    186                 }
    187                 return c3d;
    188             }
    189
    190             t = this.transformations;
    191             for (i = 0; i < t.length; i++) {
    192                 t[i].update();
    193             }
    194             if (c3d.length === 3) {
    195                 c3d.unshift(1);
    196             }
    197
    198             if (this === this.baseElement) {
    199                 if (Type.exists(this._F)) {
    200                     c3d = this._F(u);
    201                 } else {
    202                     c3d = [this._X[u], this._Y[u], this._Z[u]];
    203                 }
    204             } else {
    205                 c3d = this.baseElement.evalF(u);
    206             }
    207             c3d.unshift(1);
    208             c3d = Mat.matVecMult(t[0].matrix, c3d);
    209             for (i = 1; i < t.length; i++) {
    210                 c3d = Mat.matVecMult(t[i].matrix, c3d);
    211             }
    212
    213             return c3d.slice(1);
    214         },
    215
    216         /**
    217          * Function defining the curve plus applying transformations.
    218          * @param {Number} u
    219          * @returns Array [x, y, z] of length 3
    220          */
    221         F: function(u) {
    222             return this.evalF(u);
    223         },
    224
    225         /**
    226         * Function which maps (u) to z; i.e. it defines the x-coordinate of the curve
    227         * plus applying transformations.
    228         * @param {Number} u
    229         * @returns Number
    230         */
    231         X: function(u) {
    232             return this.evalF(u)[0];
    233         },
    234
    235         /**
    236         * Function which maps (u) to y; i.e. it defines the y-coordinate of the curve
    237         * plus applying transformations.
    238         * @param {Number} u
    239         * @returns Number
    240         */
    241         Y: function(u) {
    242             return this.evalF(u)[1];
    243         },
    244
    245         /**
    246         * Function which maps (u) to z; i.e. it defines the z-coordinate of the curve
    247         * plus applying transformations.
    248         * @param {Number} u
    249         * @returns Number
    250         */
    251         Z: function(u) {
    252             return this.evalF(u)[2];
    253         },
    254
    255         updateDataArray2D: function () {
    256             var i, c2d,
    257                 dataX = [],
    258                 dataY = [],
    259                 len = this.points.length;
    260
    261             for (i = 0; i < len; i++) {
    262                 c2d = this.view.project3DTo2D(this.points[i]);
    263                 dataX.push(c2d[1]);
    264                 dataY.push(c2d[2]);
    265             }
    266
    267             return { X: dataX, Y: dataY };
    268         },
    269
    270         // Already documented in GeometryElement
    271         addTransform: function (el, transform) {
    272             this.addTransformGeneric(el, transform);
    273             return this;
    274         },
    275
    276         /**
    277          *
    278          * @returns {JXG.Curve3D} Reference to itself
    279          */
    280         updateTransform: function () {
    281             var t, c, i, j, len;
    282
    283             if (this.transformations.length === 0 || this.baseElement === null ||
    284                 Type.exists(this._F) // Transformations have only to be applied here
    285                                      // if the curve is defined by arrays
    286             ) {
    287                 return this;
    288             }
    289
    290             t = this.transformations;
    291             for (i = 0; i < t.length; i++) {
    292                 t[i].update();
    293             }
    294             len = this.baseElement.numberPoints;
    295             for (i = 0; i < len; i++) {
    296                 if (this === this.baseElement) {
    297                     c = this.points[i];
    298                 } else {
    299                     c = this.baseElement.points[i];
    300                 }
    301                 for (j = 0; j < t.length; j++) {
    302                     c = Mat.matVecMult(t[j].matrix, c);
    303                 }
    304                 this.points[i] = c;
    305             }
    306             this.numberPoints = len;
    307
    308             return this;
    309         },
    310
    311         // Already documented in GeometryElement
    312         updateDataArray: function() { /* stub */ },
    313
    314         // Already documented in GeometryElement
    315         update: function () {
    316             if (this.needsUpdate) {
    317                 this.updateDataArray();
    318                 this.updateCoords()
    319                     .updateTransform();
    320             }
    321             return this;
    322         },
    323
    324         // Already documented in GeometryElement
    325         updateRenderer: function () {
    326             this.needsUpdate = false;
    327             return this;
    328         },
    329
    330         // Already documented in element3d.js
    331         projectCoords: function (p, params) {
    332             return Geometry.projectCoordsToParametric(p, this, 1, params);
    333         }
    334
    335         // projectScreenCoords: function (pScr, params) {
    336         //     this.initParamsIfNeeded(params);
    337         //     return Geometry.projectScreenCoordsToParametric(pScr, this, params);
    338         // }
    339     }
    340 );
    341
    342 /**
    343  * @class 3D Curves can be defined by mappings or by discrete data sets.
    344  * In general, a 3D curve is a mapping from R to R^3, where t maps to (x(t),y(t),z(t)).
    345  * The graph is drawn for t in the interval [a,b].
    346  * @pseudo
    347  * @description A 3D parametric curve is defined by a function
    348  *    <i>F: R<sup>1</sup> → R<sup>3</sup></i>.
    349  *
    350  * @name Curve3D
    351  * @augments Curve
    352  * @constructor
    353  * @type Object
    354  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    355  * @param {Function_Function_Function_Array,Function} F<sub>X</sub>,F<sub>Y</sub>,F<sub>Z</sub>,range
    356  * F<sub>X</sub>(u), F<sub>Y</sub>(u), F<sub>Z</sub>(u) are functions returning a number, range is the array containing
    357  * lower and upper bound for the range of the parameter u. range may also be a function returning an array of length two.
    358  * @param {Function_Array,Function} F,range Alternatively: F<sub>[X,Y,Z]</sub>(u) a function returning an array [x,y,z] of
    359  * numbers, range as above.
    360  * @param {Array_Array_Array} X,Y,Z Three arrays containing the coordinate points which define the curve.
    361  * @example
    362  * // create a simple curve in 3d
    363  * var bound = [-1.5, 1.5];
    364  * var view=board.create('view3d',
    365  *     [[-4, -4],[8, 8],
    366  *     [bound, bound, bound]],
    367  *     {});
    368  * var curve = view.create('curve3d', [(u)=>Math.cos(u), (u)=>Math.sin(u), (u)=>(u/Math.PI)-1,[0,2*Math.PI] ]);
    369  * </pre><div id="JXG0f35a50e-e99d-11e8-a1ca-04d3b0c2aad3" class="jxgbox" style="width: 300px; height: 300px;"></div>
    370  * <script type="text/javascript">
    371  *     (function() {
    372  *         var board = JXG.JSXGraph.initBoard('JXG0f35a50e-e99d-11e8-a1ca-04d3b0c2aad3',
    373  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
    374  *         // create a simple curve in 3d
    375  *         var bound = [-1.5, 1.5];
    376  *         var view=board.create('view3d',
    377  *             [[-4, -4],[8, 8],
    378  *             [bound, bound, bound]],
    379  *             {});
    380  *         var curve = view.create('curve3d', [(u)=>Math.cos(u), (u)=>Math.sin(u), (u)=>(u/Math.PI)-1,[0,2*Math.PI] ]);
    381  *     })();
    382  * </script><pre>
    383   */
    384 JXG.createCurve3D = function (board, parents, attributes) {
    385     var view = parents[0],
    386         F, X, Y, Z, range, attr, el,
    387         mat,
    388         base = null,
    389         transform = null;
    390
    391     if (parents.length === 3) {
    392         if (Type.isTransformationOrArray(parents[2]) && parents[1].type === Const.OBJECT_TYPE_CURVE3D) {
    393             // [curve, transformation(s)]
    394             // This might be adopted to the type of the base element (data plot or function)
    395             base = parents[1];
    396             transform = parents[2];
    397             F = null;
    398             X = [];
    399             Y = [];
    400             Z = [];
    401         } else {
    402             // [F, range]
    403             F = parents[1];
    404             range = parents[2];
    405             X = null;
    406             Y = null;
    407             Z = null;
    408         }
    409     } else if (parents.length === 2 && Type.isArray(parents[1])) {
    410         mat = Mat.transpose(parents[1]);
    411         X = mat[0];
    412         Y = mat[1];
    413         Z = mat[2];
    414         F = null;
    415     } else {
    416         // [X, Y, Z, range]
    417         X = parents[1];
    418         Y = parents[2];
    419         Z = parents[3];
    420         range = parents[4];
    421         F = null;
    422     }
    423     // TODO Throw new Error
    424
    425     attr = Type.copyAttributes(attributes, board.options, "curve3d");
    426     el = new JXG.Curve3D(view, F, X, Y, Z, range, attr);
    427
    428     attr = el.setAttr2D(attr);
    429     el.element2D = view.create("curve", [[], []], attr);
    430     el.element2D.view = view;
    431     if (base !== null) {
    432         el.addTransform(base, transform);
    433         el.addParents(base);
    434     }
    435
    436     /**
    437      * @class
    438      * @ignore
    439      */
    440     el.element2D.updateDataArray = function () {
    441         var ret = el.updateDataArray2D();
    442         this.dataX = ret.X;
    443         this.dataY = ret.Y;
    444     };
    445     el.addChild(el.element2D);
    446     el.inherits.push(el.element2D);
    447     el.element2D.setParents(el);
    448
    449     el.element2D.prepareUpdate().update();
    450     if (!board.isSuspendedUpdate) {
    451         el.element2D.updateVisibility().updateRenderer();
    452     }
    453
    454     return el;
    455 };
    456
    457 JXG.registerElement("curve3d", JXG.createCurve3D);
    458
    459 /**
    460  * @class A vector field is an assignment of a vector to each point in 3D space.
    461  * <p>
    462  * Plot a vector field either given by three functions
    463  * f1(x, y, z), f2(x, y, z), and f3(x, y, z) or by a function f(x, y, z)
    464  * returning an array of size 3.
    465  *
    466  * @pseudo
    467  * @name Vectorfield3D
    468  * @augments JXG.Curve3D
    469  * @constructor
    470  * @type JXG.Curve3D
    471  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
    472  * Parameter options:
    473  * @param {Array|Function|String} F Either an array containing three functions f1(x, y, z), f2(x, y, z),
    474  * and f3(x, y) or function f(x, y, z) returning an array of length 3.
    475  * @param {Array} xData Array of length 3 containing start value for x, number of steps,
    476  * end value of x. The vector field will contain (number of steps) + 1 vectors in direction of x.
    477  * @param {Array} yData Array of length 3 containing start value for y, number of steps,
    478  * end value of y. The vector field will contain (number of steps) + 1 vectors in direction of y.
    479  * @param {Array} zData Array of length 3 containing start value for z, number of steps,
    480  * end value of z. The vector field will contain (number of steps) + 1 vectors in direction of z.
    481  *
    482  * @example
    483  * const view = board.create('view3d',
    484  *     [
    485  *         [-6, -3],
    486  *         [8, 8],
    487  *         [[-3, 3], [-3, 3], [-3, 3]]
    488  *     ], {});
    489  *
    490  * var vf = view.create('vectorfield3d', [
    491  *     [(x, y, z) => Math.cos(y), (x, y, z) => Math.sin(x), (x, y, z) => z],
    492  *     [-2, 5, 2], // x from -2 to 2 in 5 steps
    493  *     [-2, 5, 2], // y
    494  *     [-2, 5, 2] // z
    495  * ], {
    496  *     strokeColor: 'red',
    497  *     scale: 0.5
    498  * });
    499  *
    500  * </pre><div id="JXG8e41c67b-3338-4428-bd0f-c69d8f6fb348" class="jxgbox" style="width: 300px; height: 300px;"></div>
    501  * <script type="text/javascript">
    502  *     (function() {
    503  *         var board = JXG.JSXGraph.initBoard('JXG8e41c67b-3338-4428-bd0f-c69d8f6fb348',
    504  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false,
    505  *          pan: {
    506  *                needTwoFingers: true
    507  *           }
    508  *          });
    509  *     const view = board.create('view3d',
    510  *         [
    511  *             [-6, -3],
    512  *             [8, 8],
    513  *             [[-3, 3], [-3, 3], [-3, 3]]
    514  *         ], {});
    515  *     var vf = view.create('vectorfield3d', [
    516  *         [(x, y, z) => Math.cos(y), (x, y, z) => Math.sin(x), (x, y, z) => z],
    517  *         [-2, 5, 2], // x from -2 to 2 in 5 steps
    518  *         [-2, 5, 2], // y
    519  *         [-2, 5, 2] // z
    520  *     ], {
    521  *         strokeColor: 'red',
    522  *         scale: 0.5
    523  *     });
    524  *
    525  *
    526  *     })();
    527  *
    528  * </script><pre>
    529  *
    530  */
    531 JXG.createVectorfield3D = function (board, parents, attributes) {
    532     var view = parents[0],
    533         el, attr;
    534
    535     if (!(parents.length >= 5 &&
    536         (Type.isArray(parents[1]) || Type.isFunction(parents[1]) || Type.isString(parents[1])) &&
    537         (Type.isArray(parents[2]) && parents[1].length === 3) &&
    538         (Type.isArray(parents[3]) && parents[2].length === 3) &&
    539         (Type.isArray(parents[4]) && parents[3].length === 3)
    540     )) {
    541         throw new Error(
    542             "JSXGraph: Can't create vector field 3D with parent types " +
    543             "'" + typeof parents[1] + "', " +
    544             "'" + typeof parents[2] + "', " +
    545             "'" + typeof parents[3] + "'."  +
    546             "'" + typeof parents[4] + "', "
    547         );
    548     }
    549
    550     attr = Type.copyAttributes(attributes, board.options, 'vectorfield3d');
    551     el = view.create('curve3d', [[], [], []], attr);
    552
    553     /**
    554      * Set the defining functions of 3D vector field.
    555      * @memberOf Vectorfield3D
    556      * @name setF
    557      * @function
    558      * @param {Array|Function} func Either an array containing three functions f1(x, y, z),
    559      * f2(x, y, z), and f3(x, y, z) or function f(x, y, z) returning an array of length 3.
    560      * @returns {Object} Reference to the 3D vector field object.
    561      *
    562      * @example
    563      * field.setF([(x, y, z) => Math.sin(y), (x, y, z) => Math.cos(x), (x, y, z) => z]);
    564      * board.update();
    565      *
    566      */
    567     el.setF = function (func, varnames) {
    568         var f0, f1, f2;
    569         if (Type.isArray(func)) {
    570             f0 = Type.createFunction(func[0], this.board, varnames);
    571             f1 = Type.createFunction(func[1], this.board, varnames);
    572             f2 = Type.createFunction(func[2], this.board, varnames);
    573             /**
    574              * @ignore
    575              */
    576             this.F = function (x, y, z) {
    577                 return [f0(x, y, z), f1(x, y, z), f2(x, y, z)];
    578             };
    579         } else {
    580             this.F = Type.createFunction(func, el.board, varnames);
    581         }
    582         return this;
    583     };
    584
    585     el.setF(parents[1], 'x, y, z');
    586     el.xData = parents[2];
    587     el.yData = parents[3];
    588     el.zData = parents[4];
    589
    590     el.updateDataArray = function () {
    591         var k, i, j,
    592             v, nrm,
    593             x, y, z,
    594             scale = this.evalVisProp('scale'),
    595             start = [
    596                 Type.evaluate(this.xData[0]),
    597                 Type.evaluate(this.yData[0]),
    598                 Type.evaluate(this.zData[0])
    599             ],
    600             steps = [
    601                 Type.evaluate(this.xData[1]),
    602                 Type.evaluate(this.yData[1]),
    603                 Type.evaluate(this.zData[1])
    604             ],
    605             end = [
    606                 Type.evaluate(this.xData[2]),
    607                 Type.evaluate(this.yData[2]),
    608                 Type.evaluate(this.zData[2])
    609             ],
    610             delta = [
    611                 (end[0] - start[0]) / steps[0],
    612                 (end[1] - start[1]) / steps[1],
    613                 (end[2] - start[2]) / steps[2]
    614             ],
    615             phi, theta1, theta2, theta,
    616             showArrow = this.evalVisProp('arrowhead.enabled'),
    617             leg, leg_x, leg_y, leg_z, alpha;
    618
    619         if (showArrow) {
    620             // Arrow head style
    621             // leg = 8;
    622             // alpha = Math.PI * 0.125;
    623             leg = this.evalVisProp('arrowhead.size');
    624             alpha = this.evalVisProp('arrowhead.angle');
    625             leg_x = leg / board.unitX;
    626             leg_y = leg / board.unitY;
    627             leg_z = leg / Math.sqrt(board.unitX * board.unitY);
    628         }
    629
    630         this.dataX = [];
    631         this.dataY = [];
    632         this.dataZ = [];
    633         for (i = 0, x = start[0]; i <= steps[0]; x += delta[0], i++) {
    634             for (j = 0, y = start[1]; j <= steps[1]; y += delta[1], j++) {
    635                 for (k = 0, z = start[2]; k <= steps[2]; z += delta[2], k++) {
    636                     v = this.F(x, y, z);
    637                     nrm = Mat.norm(v);
    638                     if (nrm < Number.EPSILON) {
    639                         continue;
    640                     }
    641
    642                     v[0] *= scale;
    643                     v[1] *= scale;
    644                     v[2] *= scale;
    645                     Type.concat(this.dataX, [x, x + v[0], NaN]);
    646                     Type.concat(this.dataY, [y, y + v[1], NaN]);
    647                     Type.concat(this.dataZ, [z, z + v[2], NaN]);
    648
    649                     if (showArrow) {
    650                         // Arrow head
    651                         nrm *= scale;
    652                         phi = Math.atan2(v[1], v[0]);
    653                         theta = Math.asin(v[2] / nrm);
    654                         theta1 = theta - alpha;
    655                         theta2 = theta + alpha;
    656                         Type.concat(this.dataX, [
    657                             x + v[0] - leg_x * Math.cos(phi) * Math.cos(theta1),
    658                             x + v[0],
    659                             x + v[0] - leg_x * Math.cos(phi) * Math.cos(theta2),
    660                             NaN]);
    661                         Type.concat(this.dataY, [
    662                             y + v[1] - leg_y * Math.sin(phi) * Math.cos(theta1),
    663                             y + v[1],
    664                             y + v[1] - leg_y * Math.sin(phi) * Math.cos(theta2),
    665                             NaN]);
    666                         Type.concat(this.dataZ, [
    667                             z + v[2] - leg_z * Math.sin(theta2),
    668                             z + v[2],
    669                             z + v[2] - leg_z * Math.sin(theta1),
    670                             NaN]);
    671                     }
    672                 }
    673             }
    674         }
    675     };
    676
    677     el.methodMap = Type.deepCopy(el.methodMap, {
    678         setF: "setF"
    679     });
    680
    681     return el;
    682 };
    683
    684 JXG.registerElement("vectorfield3D", JXG.createVectorfield3D);
    685