      1 /*
      2     Copyright 2008-2023
      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 /**
     32  * Create linear spaces of dimension at least one,
     33  * i.e. lines and planes.
     34  */
     35 import JXG from '../jxg';
     36 import Const from '../base/constants';
     37 import Type from '../utils/type';
     38 import Mat from '../math/math';
     39 import Geometry from '../math/geometry';
     40
     41 // -----------------------
     42 //  Lines
     43 // -----------------------
     44
     45 /**
     46  * Constructor for 3D lines.
     47  * @class Creates a new 3D line object. Do not use this constructor to create a 3D line. Use {@link JXG.View3D#create} with type {@link Line3D} instead.
     48  *
     49  * @augments JXG.GeometryElement3D
     50  * @augments JXG.GeometryElement
     51  * @param {View3D} view
     52  * @param {Point3D|Array} point
     53  * @param {Array} direction
     54  * @param {Array} range
     55  * @param {Object} attributes
     56  * @see JXG.Board#generateName
     57  */
     58 JXG.Line3D = function (view, point, direction, range, attributes) {
     59     this.constructor(view.board, attributes, Const.OBJECT_TYPE_LINE3D, Const.OBJECT_CLASS_3D);
     60     this.constructor3D(view, 'line3d');
     61
     62     this.board.finalizeAdding(this);
     63
     64     /**
     65      * 3D point which - together with a direction - defines the line.
     66      * @type JXG.Point3D
     67      *
     68      * @see JXG.Line3D#direction
     69      */
     70     this.point = point;
     71
     72     /**
     73      * Direction which - together with a point - defines the line. Array of numbers or functions (of length 3) or function
     74      * returning array of length 3.
     75      *
     76      * @type Array|Function
     77      * @see JXG.Line3D#point
     78      */
     79     this.direction = direction;
     80
     81     /**
     82      * Range [r1, r2] of the line. r1, r2 can be numbers or functions.
     83      * The 3D line goes from (point + r1 * direction) to (point + r2 * direction)
     84      * @type Array
     85      */
     86     this.range = range || [-Infinity, Infinity];
     87
     88     /**
     89      * Starting point of the 3D line
     90      * @type JXG.Point3D
     91      * @private
     92      */
     93     this.point1 = null;
     94
     95     /**
     96      * End point of the 3D line
     97      * @type JXG.Point3D
     98      * @private
     99      */
    100     this.point2 = null;
    101
    102     this.methodMap = Type.deepCopy(this.methodMap, {
    103         // TODO
    104     });
    105 };
    106 JXG.Line3D.prototype = new JXG.GeometryElement();
    107 Type.copyPrototypeMethods(JXG.Line3D, JXG.GeometryElement3D, 'constructor3D');
    108
    109 JXG.extend(
    110     JXG.Line3D.prototype,
    111     /** @lends JXG.Line3D.prototype */ {
    112         /**
    113          * Determine one end point of a 3D line from point, direction and range.
    114          *
    115          * @param {Number|function} r
    116          * @private
    117          * @returns Array
    118          */
    119         getPointCoords: function (r) {
    120             var p = [],
    121                 d = [],
    122                 i,
    123                 r0;
    124
    125             p = [this.point.X(), this.point.Y(), this.point.Z()];
    126
    127             if (Type.isFunction(this.direction)) {
    128                 d = this.direction();
    129             } else {
    130                 for (i = 1; i < 4; i++) {
    131                     d.push(Type.evaluate(this.direction[i]));
    132                 }
    133             }
    134
    135             r0 = Type.evaluate(r);
    136             // TODO: test also in the finite case
    137             if (Math.abs(r0) === Infinity) {
    138                 r = this.view.intersectionLineCube(p, d, r0);
    139             }
    140
    141             return [p[0] + d[0] * r0, p[1] + d[1] * r0, p[2] + d[2] * r0];
    142         },
    143
    144         update: function () {
    145             return this;
    146         },
    147
    148         updateRenderer: function () {
    149             this.needsUpdate = false;
    150             return this;
    151         }
    152     }
    153 );
    154
    155 /**
    156  * @class This element is used to provide a constructor for a 3D line.
    157  * @pseudo
    158  * @description There are two possibilities to create a Line3D object.
    159  * <p>
    160  * First: the line in 3D is defined by two points in 3D (Point3D).
    161  * The points can be either existing points or coordinate arrays of
    162  * the form [x, y, z].
    163  * <p>Second: the line in 3D is defined by a point (or coordinate array [x, y, z])
    164  * a direction given as array [x, y, z] and an optional range
    165  * given as array [s, e]. The default value for the range is [-Infinity, Infinity].
    166  * <p>
    167  * All numbers can also be provided as functions returning a number.
    168  *
    169  * @name Line3D
    170  * @augments JXG.GeometryElement3D
    171  * @constructor
    172  * @type JXG.Line3D
    173  * @throws {Exception} If the element cannot be constructed with the given parent
    174  * objects an exception is thrown.
    175  * @param {JXG.Point3D,array,function_JXG.Point3D,array,function} point1,point2 First and second defining point.
    176  * @param {JXG.Point3D,array,function_array,function_array,function} point,direction,range Alternatively, point, direction and range can be supplied.
    177  * <ul>
    178  * <li> point: Point3D or array of length 3
    179  * <li> direction: array of length 3 or function returning an array of numbers or function returning an array
    180  * <li> range: array of length 2, elements can also be functions.
    181  * </ul>
    182  *
    183  * @example
    184  *     var bound = [-5, 5];
    185  *     var view = board.create('view3d',
    186  *         [[-6, -3], [8, 8],
    187  *         [bound, bound, bound]],
    188  *         {});
    189  *     var p = view.create('point3d', [1, 2, 2], { name:'A', size: 5 });
    190  *     // Lines through 2 points
    191  *     var l1 = view.create('line3d', [[1, 3, 3], [-3, -3, -3]], {point1: {visible: true}, point2: {visible: true} });
    192  *     var l2 = view.create('line3d', [p, l1.point1]);
    193  *
    194  *     // Line by point, direction, range
    195  *     var l3 = view.create('line3d', [p, [0, 0, 1], [-2, 4]]);
    196  *
    197  * </pre><div id='JXG05f9baa4-6059-4502-8911-6a934f823b3d' class='jxgbox' style='width: 300px; height: 300px;'></div>
    198  * <script type='text/javascript'>
    199  *     (function() {
    200  *         var board = JXG.JSXGraph.initBoard('JXG05f9baa4-6059-4502-8911-6a934f823b3d',
    201  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
    202  *         var bound = [-5, 5];
    203  *         var view = board.create('view3d',
    204  *             [[-6, -3], [8, 8],
    205  *             [bound, bound, bound]],
    206  *             {});
    207  *         var p = view.create('point3d', [1, 2, 2], { name:'A', size: 5 });
    208  *         // Lines through 2 points
    209  *         var l1 = view.create('line3d', [[1, 3, 3], [-3, -3, -3]], {name: 'll1', point1: {visible: true}, point2: {visible: true} });
    210  *         var l2 = view.create('line3d', [p, l1.point1]);
    211  *         // Line by point, direction, range
    212  *         var l3 = view.create('line3d', [p, [0, 0, 1], [-2, 4]]);
    213  *     })();
    214  *
    215  * </script><pre>
    216  *
    217  */
    218 JXG.createLine3D = function (board, parents, attributes) {
    219     var view = parents[0],
    220         attr, points,
    221         point, direction, range,
    222         point1, point2, el;
    223
    224     attr = Type.copyAttributes(attributes, board.options, 'line3d');
    225
    226     // In any case, parents[1] contains a point or point coordinates
    227
    228     if (
    229         Type.isPoint3D(parents[2]) ||
    230         (parents.length === 3 && (Type.isArray(parents[2]) || Type.isFunction(parents[2])))
    231     ) {
    232         // Line defined by two points; [view, point1, point2]
    233
    234         point1 = Type.providePoints3D(view, [parents[1]], attributes, 'line3d', ['point1'])[0];
    235         point2 = Type.providePoints3D(view, [parents[2]], attributes, 'line3d', ['point2'])[0];
    236         direction = function () {
    237             return [point2.X() - point1.X(), point2.Y() - point1.Y(), point2.Z() - point1.Z()];
    238         };
    239         range = [0, 1];
    240         el = new JXG.Line3D(view, point1, direction, range, attr);
    241     } else {
    242         // Line defined by point, direction and range
    243         point = Type.providePoints3D(view, [parents[1]], attributes, 'line3d', ['point'])[0];
    244
    245         // Directions are handled as arrays of length 4,
    246         // i.e. with homogeneous coordinates.
    247         if (Type.isFunction(parents[2])) {
    248             direction = parents[2];
    249         } else if (parents[2].length === 3) {
    250             direction = [1].concat(parents[2]);
    251         } else if (parents[2].length === 4) {
    252             direction = parents[2];
    253         } else {
    254             // TODO Throw error
    255         }
    256         range = parents[3];
    257
    258         points = Type.providePoints3D(
    259             view,
    260             [
    261                 [0, 0, 0],
    262                 [0, 0, 0]
    263             ],
    264             attributes,
    265             'line3d',
    266             ['point1', 'point2']
    267         );
    268
    269         // Create a line3d with two dummy points
    270         el = new JXG.Line3D(view, point, direction, range, attr);
    271
    272         // Now set the real points which define the line
    273         /**
    274          * @class
    275          * @ignore
    276          */
    277         points[0].F = function () {
    278             return el.getPointCoords(Type.evaluate(el.range[0]));
    279         };
    280         points[0].prepareUpdate().update();
    281         point1 = points[0];
    282
    283         /**
    284          * @class
    285          * @ignore
    286          */
    287         points[1].F = function () {
    288             return el.getPointCoords(Type.evaluate(el.range[1]));
    289         };
    290         points[1].prepareUpdate().update();
    291         point2 = points[1];
    292     }
    293     // TODO Throw error
    294
    295     attr = el.setAttr2D(attr);
    296     el.element2D = view.create('segment', [point1.element2D, point2.element2D], attr);
    297     el.addChild(el.element2D);
    298     el.inherits.push(el.element2D);
    299     el.element2D.setParents(el);
    300     // el.setParents([point1.id, point2.id]);
    301
    302     el.point1 = point1;
    303     el.point2 = point2;
    304     if (el.point1._is_new) {
    305         el.addChild(el.point1);
    306         delete el.point1._is_new;
    307     } else {
    308         el.point1.addChild(el);
    309     }
    310     if (el.point2._is_new) {
    311         el.addChild(el.point2);
    312         delete el.point2._is_new;
    313     } else {
    314         el.point2.addChild(el);
    315     }
    316     if (Type.exists(point)) {
    317         if (point._is_new) {
    318             el.addChild(point);
    319             delete point._is_new;
    320         } else {
    321             point.addChild(el);
    322         }
    323     }
    324
    325     el.update();
    326     el.element2D.prepareUpdate().update().updateRenderer();
    327     return el;
    328 };
    329 JXG.registerElement('line3d', JXG.createLine3D);
    330
    331 // -----------------------
    332 //  Planes
    333 // -----------------------
    334
    335 /**
    336  * Constructor for 3D planes.
    337  * @class Creates a new 3D plane object. Do not use this constructor to create a 3D plane. Use {@link JXG.Board#create} with type {@link Plane3D} instead.
    338  *
    339  * @augments JXG.GeometryElement3D
    340  * @augments JXG.GeometryElement
    341  * @param {View3D} view
    342  * @param {Point3D|Array} point
    343  * @param {Array} direction1
    344  * @param {Array} range1
    345  * @param {Array} direction2
    346  * @param {Array} range2
    347  * @param {Object} attributes
    348  * @see JXG.Board#generateName
    349  */
    350 JXG.Plane3D = function (view, point, dir1, range1, dir2, range2, attributes) {
    351     this.constructor(view.board, attributes, Const.OBJECT_TYPE_PLANE3D, Const.OBJECT_CLASS_3D);
    352     this.constructor3D(view, 'plane3d');
    353
    354     this.board.finalizeAdding(this);
    355
    356     /**
    357      * 3D point which - together with two direction vectors - defines the plane.
    358      *
    359      * @type JXG.Point3D
    360      *
    361      * @see JXG.3D#direction1
    362      * @see JXG.3D#direction2
    363      */
    364     this.point = point;
    365
    366     /**
    367      * Two linearly independent vectors - together with a point - define the plane. Each of these direction vectors is an
    368      * array of numbers or functions (of length 3) or function returning array of length 3.
    369      *
    370      * @type Array|Function
    371      *
    372      * @see JXG.Plane3D#point
    373      * @see JXG.Plane3D#direction2
    374      */
    375     this.direction1 = dir1;
    376
    377     /**
    378      * Two linearly independent vectors - together with a point - define the plane. Each of these direction vectors is an
    379      * array of numbers or functions (of length 3) or function returning array of length 3.
    380      *
    381      * @type Array|Function
    382      * @see JXG.Plane3D#point
    383      * @see JXG.Plane3D#direction1
    384      */
    385     this.direction2 = dir2;
    386
    387     /**
    388      * Range [r1, r2] of {@link direction1}. The 3D line goes from (point + r1 * direction1) to (point + r2 * direction1)
    389      * @type {Array}
    390      */
    391     this.range1 = range1 || [-Infinity, Infinity];
    392
    393     /**
    394      * Range [r1, r2] of {@link direction2}. The 3D line goes from (point + r1 * direction2) to (point + r2 * direction2)
    395      * @type {Array}
    396      */
    397     this.range2 = range2 || [-Infinity, Infinity];
    398
    399     /**
    400      * Direction vector 1 of the 3D plane. Contains the evaluated coordinates from {@link direction1} and {@link range1}.
    401      * @type Array
    402      * @private
    403      *
    404      * @see updateNormal
    405      */
    406     this.vec1 = [0, 0, 0];
    407
    408     /**
    409      * Direction vector 2 of the 3D plane. Contains the evaluated coordinates from {@link direction2} and {@link range2}.
    410      *
    411      * @type Array
    412      * @private
    413      *
    414      * @see updateNormal
    415      */
    416     this.vec2 = [0, 0, 0];
    417
    418     this.grid = null;
    419
    420     /**
    421          * Normal vector of the plane. Left hand side of the Hesse normal form.
    422
    423         * @type Array
    424          * @private
    425          *
    426          * @see updateNormal
    427          *
    428          */
    429     this.normal = [0, 0, 0];
    430
    431     /**
    432          * Right hand side of the Hesse normal form.
    433
    434         * @type Array
    435          * @private
    436          *
    437          * @see updateNormal
    438          *
    439          */
    440     this.d = 0;
    441
    442     this.updateNormal();
    443
    444     this.methodMap = Type.deepCopy(this.methodMap, {
    445         // TODO
    446     });
    447 };
    448 JXG.Plane3D.prototype = new JXG.GeometryElement();
    449 Type.copyPrototypeMethods(JXG.Plane3D, JXG.GeometryElement3D, 'constructor3D');
    450
    451 JXG.extend(
    452     JXG.Plane3D.prototype,
    453     /** @lends JXG.Plane3D.prototype */ {
    454         /**
    455          * Update the Hesse normal form of the plane, i.e. update normal vector and right hand side.
    456          * Updates also {@link vec1} and {@link vec2}.
    457          *
    458          * @name JXG.Plane3D#updateNormal
    459          * @function
    460          * @returns {Object} Reference to the Plane3D object
    461          * @private
    462          * @example
    463          *    plane.updateNormal();
    464          *
    465          */
    466         updateNormal: function () {
    467             var i, len;
    468             for (i = 0; i < 3; i++) {
    469                 this.vec1[i] = Type.evaluate(this.direction1[i]);
    470                 this.vec2[i] = Type.evaluate(this.direction2[i]);
    471             }
    472
    473             this.normal = Mat.crossProduct(this.vec1, this.vec2);
    474
    475             len = Mat.norm(this.normal);
    476             if (Math.abs(len) > Mat.eps) {
    477                 for (i = 0; i < 3; i++) {
    478                     this.normal[i] /= len;
    479                 }
    480             }
    481             this.d = Mat.innerProduct(this.point.coords.slice(1), this.normal, 3);
    482
    483             return this;
    484         },
    485
    486         updateDataArray: function () {
    487             var s1, e1, s2, e2, c2d, l1, l2,
    488                 planes = ['xPlaneRear', 'yPlaneRear', 'zPlaneRear'],
    489                 points = [],
    490                 v1 = [0, 0, 0],
    491                 v2 = [0, 0, 0],
    492                 q = [0, 0, 0],
    493                 p = [0, 0, 0],
    494                 d, i, j, a, b, first, pos, pos_akt,
    495                 view = this.view;
    496
    497             this.dataX = [];
    498             this.dataY = [];
    499
    500             this.updateNormal();
    501
    502             // Infinite plane
    503             if (
    504                 this.elType !== 'axisplane3d' &&
    505                 view.defaultAxes &&
    506                 Type.evaluate(this.range1[0]) === -Infinity &&
    507                 Type.evaluate(this.range1[1]) === Infinity &&
    508                 Type.evaluate(this.range2[0]) === -Infinity &&
    509                 Type.evaluate(this.range2[1]) === Infinity
    510             ) {
    511                 // Start with the rear plane.
    512                 // Determine the intersections with the view bbox3d
    513                 // For each face of the bbox3d we determine two points
    514                 // which are the ends of the intersection line.
    515                 // We start with the three rear planes.
    516                 for (j = 0; j < planes.length; j++) {
    517                     p = view.intersectionPlanePlane(this, view.defaultAxes[planes[j]]);
    518
    519                     if (p[0].length === 3 && p[1].length === 3) {
    520                         // This test is necessary to filter out intersection lines which are
    521                         // identical to intersections of axis planes (they would occur twice).
    522                         for (i = 0; i < points.length; i++) {
    523                             if (
    524                                 (Geometry.distance(p[0], points[i][0], 3) < Mat.eps &&
    525                                     Geometry.distance(p[1], points[i][1], 3) < Mat.eps) ||
    526                                 (Geometry.distance(p[0], points[i][1], 3) < Mat.eps &&
    527                                     Geometry.distance(p[1], points[i][0], 3) < Mat.eps)
    528                             ) {
    529                                 break;
    530                             }
    531                         }
    532                         if (i === points.length) {
    533                             points.push(p.slice());
    534                         }
    535                     }
    536
    537                     // Point on the front plane of the bbox3d
    538                     p = [0, 0, 0];
    539                     p[j] = view.bbox3D[j][1];
    540
    541                     // d is the rhs of the Hesse normal form of the front plane.
    542                     d = Mat.innerProduct(p, view.defaultAxes[planes[j]].normal, 3);
    543                     p = view.intersectionPlanePlane(this, view.defaultAxes[planes[j]], d);
    544
    545                     if (p[0].length === 3 && p[1].length === 3) {
    546                         // Do the same test as above
    547                         for (i = 0; i < points.length; i++) {
    548                             if (
    549                                 (Geometry.distance(p[0], points[i][0], 3) < Mat.eps &&
    550                                     Geometry.distance(p[1], points[i][1], 3) < Mat.eps) ||
    551                                 (Geometry.distance(p[0], points[i][1], 3) < Mat.eps &&
    552                                     Geometry.distance(p[1], points[i][0], 3) < Mat.eps)
    553                             ) {
    554                                 break;
    555                             }
    556                         }
    557                         if (i === points.length) {
    558                             points.push(p.slice());
    559                         }
    560                     }
    561                 }
    562
    563                 // Concatenate the intersection points to a polygon.
    564                 // If all wents well, each intersection should appear
    565                 // twice in the list.
    566                 first = 0;
    567                 pos = first;
    568                 i = 0;
    569                 do {
    570                     p = points[pos][i];
    571                     if (p.length === 3) {
    572                         c2d = view.project3DTo2D(p);
    573                         this.dataX.push(c2d[1]);
    574                         this.dataY.push(c2d[2]);
    575                     }
    576                     i = (i + 1) % 2;
    577                     p = points[pos][i];
    578
    579                     pos_akt = pos;
    580                     for (j = 0; j < points.length; j++) {
    581                         if (j !== pos && Geometry.distance(p, points[j][0]) < Mat.eps) {
    582                             pos = j;
    583                             i = 0;
    584                             break;
    585                         }
    586                         if (j !== pos && Geometry.distance(p, points[j][1]) < Mat.eps) {
    587                             pos = j;
    588                             i = 1;
    589                             break;
    590                         }
    591                     }
    592                     if (pos === pos_akt) {
    593                         console.log('Error: update plane3d: did not find next', pos);
    594                         break;
    595                     }
    596                 } while (pos !== first);
    597
    598                 c2d = view.project3DTo2D(points[first][0]);
    599                 this.dataX.push(c2d[1]);
    600                 this.dataY.push(c2d[2]);
    601             } else {
    602                 // 3D bounded flat
    603                 s1 = Type.evaluate(this.range1[0]);
    604                 e1 = Type.evaluate(this.range1[1]);
    605                 s2 = Type.evaluate(this.range2[0]);
    606                 e2 = Type.evaluate(this.range2[1]);
    607
    608                 q = this.point.coords.slice(1);
    609
    610                 v1 = this.vec1.slice();
    611                 v2 = this.vec2.slice();
    612                 l1 = Mat.norm(v1, 3);
    613                 l2 = Mat.norm(v2, 3);
    614                 for (i = 0; i < 3; i++) {
    615                     v1[i] /= l1;
    616                     v2[i] /= l2;
    617                 }
    618
    619                 for (j = 0; j < 4; j++) {
    620                     switch (j) {
    621                         case 0:
    622                             a = s1;
    623                             b = s2;
    624                             break;
    625                         case 1:
    626                             a = e1;
    627                             b = s2;
    628                             break;
    629                         case 2:
    630                             a = e1;
    631                             b = e2;
    632                             break;
    633                         case 3:
    634                             a = s1;
    635                             b = e2;
    636                     }
    637                     for (i = 0; i < 3; i++) {
    638                         p[i] = q[i] + a * v1[i] + b * v2[i];
    639                     }
    640                     c2d = view.project3DTo2D(p);
    641                     this.dataX.push(c2d[1]);
    642                     this.dataY.push(c2d[2]);
    643                 }
    644                 // Close the curve
    645                 this.dataX.push(this.dataX[0]);
    646                 this.dataY.push(this.dataY[0]);
    647             }
    648             return { X: this.dataX, Y: this.dataY };
    649         },
    650
    651         update: function () {
    652             return this;
    653         },
    654
    655         updateRenderer: function () {
    656             this.needsUpdate = false;
    657             return this;
    658         }
    659     }
    660 );
    661
    662 // TODO docs
    663 JXG.createPlane3D = function (board, parents, attributes) {
    664     var view = parents[0],
    665         attr,
    666         point,
    667         dir1 = parents[2],
    668         dir2 = parents[3],
    669         range1 = parents[4] || [-Infinity, Infinity],
    670         range2 = parents[5] || [-Infinity, Infinity],
    671         el,
    672         grid;
    673
    674     point = Type.providePoints3D(view, [parents[1]], attributes, 'plane3d', ['point'])[0];
    675     if (point === false) {
    676         // TODO Throw error
    677     }
    678
    679     attr = Type.copyAttributes(attributes, board.options, 'plane3d');
    680     el = new JXG.Plane3D(view, point, dir1, range1, dir2, range2, attr);
    681     point.addChild(el);
    682
    683     attr = el.setAttr2D(attr);
    684     el.element2D = view.create('curve', [[], []], attr);
    685
    686     /**
    687      * @class
    688      * @ignore
    689      */
    690     el.element2D.updateDataArray = function () {
    691         var ret = el.updateDataArray();
    692         this.dataX = ret.X;
    693         this.dataY = ret.Y;
    694     };
    695     el.addChild(el.element2D);
    696     el.inherits.push(el.element2D);
    697     el.element2D.setParents(el);
    698
    699     attr = Type.copyAttributes(attributes.mesh3d, board.options, 'mesh3d');
    700     if (
    701         Math.abs(el.range1[0]) !== Infinity &&
    702         Math.abs(el.range1[1]) !== Infinity &&
    703         Math.abs(el.range2[0]) !== Infinity &&
    704         Math.abs(el.range2[1]) !== Infinity
    705     ) {
    706         grid = view.create('mesh3d', [
    707                 function () {
    708                     return point.coords;
    709                 },
    710                 dir1, range1, dir2, range2
    711             ], attr
    712         );
    713         el.grid = grid;
    714         el.addChild(grid);
    715         el.inherits.push(grid);
    716         grid.setParents(el);
    717     }
    718
    719     el.element2D.prepareUpdate().update();
    720     if (!board.isSuspendedUpdate) {
    721         el.element2D.updateVisibility().updateRenderer();
    722     }
    723
    724     return el;
    725 };
    726 JXG.registerElement('plane3d', JXG.createPlane3D);
    727