      1 /*
      2     Copyright 2008-2025
      3         Matthias Ehmann,
      4         Aaron Fenyes,
      5         Carsten Miller,
      6         Andreas Walter,
      7         Alfred Wassermann
      8
      9     This file is part of JSXGraph.
     10
     11     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
     12
     13     You can redistribute it and/or modify it under the terms of the
     14
     15       * GNU Lesser General Public License as published by
     16         the Free Software Foundation, either version 3 of the License, or
     17         (at your option) any later version
     18       OR
     19       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
     20
     21     JSXGraph is distributed in the hope that it will be useful,
     22     but WITHOUT ANY WARRANTY; without even the implied warranty of
     23     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     24     GNU Lesser General Public License for more details.
     25
     26     You should have received a copy of the GNU Lesser General Public License and
     27     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
     28     and <https://opensource.org/licenses/MIT/>.
     29  */
     30 /*global JXG:true, define: true*/
     31
     32 /**
     33  * Create linear spaces of dimension at least one,
     34  * i.e. lines and planes.
     35  */
     36 import JXG from '../jxg.js';
     37 import Const from '../base/constants.js';
     38 import Type from '../utils/type.js';
     39 import Mat from '../math/math.js';
     40 import Geometry from '../math/geometry.js';
     41
     42 // -----------------------
     43 //  Lines
     44 // -----------------------
     45
     46 /**
     47  * Constructor for 3D lines.
     48  * @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.
     49  *
     50  * @augments JXG.GeometryElement3D
     51  * @augments JXG.GeometryElement
     52  * @param {View3D} view
     53  * @param {Point3D|Array} point
     54  * @param {Array} direction
     55  * @param {Array} range
     56  * @param {Object} attributes
     57  * @see JXG.Board#generateName
     58  */
     59 JXG.Line3D = function (view, point, direction, range, attributes) {
     60     this.constructor(view.board, attributes, Const.OBJECT_TYPE_LINE3D, Const.OBJECT_CLASS_3D);
     61     this.constructor3D(view, 'line3d');
     62
     63     /**
     64      * 3D point which - together with a direction - defines the line.
     65      * @name point
     66      * @memberOf Line3D
     67      * @type Point3D
     68      *
     69      * @see Line3D#direction
     70      */
     71     this.point = point;
     72
     73     /**
     74      * Direction which - together with a point - defines the line. Array of numbers or functions (of length 3) or function
     75      * returning array of length 3.
     76      *
     77      * @name Line3D#direction
     78      * @type Array|Function
     79      * @see Line3D.point
     80      */
     81     this.direction = direction;
     82
     83     /**
     84      * Spanning vector of the 3D line. Contains the evaluated coordinates from {@link direction}
     85      * and {@link range}.
     86      * The array has length 4, the first entry being 0.
     87      *
     88      * @name Line3D#vec
     89      * @type {Array}
     90      */
     91     this.vec = [0, 0, 0, 0];
     92
     93     /**
     94      * Range [r1, r2] of the line. r1, r2 can be numbers or functions.
     95      * The 3D line goes from (point + r1 * direction) to (point + r2 * direction)
     96      * @name Line3D#range
     97      * @type Array
     98      * @default [-Infinity, Infinity]
     99      */
    100     this.range = range || [-Infinity, Infinity];
    101
    102     /**
    103      * Starting point of the 3D line
    104      * @name Line3D#point1
    105      * @type JXG.Point3D
    106      * @private
    107      */
    108     this.point1 = null;
    109
    110     /**
    111      * End point of the 3D line
    112      * @name Line3D#point2
    113      * @type JXG.Point3D
    114      * @private
    115      */
    116     this.point2 = null;
    117
    118     this.board.finalizeAdding(this);
    119
    120     this.methodMap = Type.deepCopy(this.methodMap, {
    121         // TODO
    122     });
    123 };
    124 JXG.Line3D.prototype = new JXG.GeometryElement();
    125 Type.copyPrototypeMethods(JXG.Line3D, JXG.GeometryElement3D, 'constructor3D');
    126
    127 JXG.extend(
    128     JXG.Line3D.prototype,
    129     /** @lends JXG.Line3D.prototype */ {
    130
    131         /**
    132          * Update the array {@link Line3D#vec} containing the homogeneous coords of the spanning vector.
    133          *
    134          * @name Line3D#updateCoords
    135          * @function
    136          * @returns {Object} Reference to Line3D object
    137          * @private
    138          */
    139         updateCoords: function() {
    140             var i,
    141                 s = 0;
    142
    143             if ((Type.exists(this.direction.view) && this.direction.type === Const.OBJECT_TYPE_LINE3D)) {
    144                 // direction is another line3D object
    145                 this.vec = this.direction.vec.slice();
    146             } else if (Type.isFunction(this.direction)) {
    147                 this.vec = Type.evaluate(this.direction);
    148                 if (this.vec.length === 3) {
    149                     this.vec.unshift(0);
    150                 }
    151             } else {
    152                 if (this.direction.length === 3) {
    153                     this.vec[0] = 0;
    154                     s = 1;
    155                 }
    156                 for (i = 0; i < this.direction.length; i++) {
    157                     this.vec[s + i] = Type.evaluate(this.direction[i]);
    158                 }
    159             }
    160
    161             return this;
    162         },
    163
    164         /**
    165          * Determine one end point of a 3D line from point, direction and range).
    166          *
    167          * @name Line3D#getPointCoords
    168          * @param {Number|function} r Usually, one of the range borders.
    169          * @private
    170          * @returns {Array} Coordinates of length 4.
    171          */
    172         getPointCoords: function (r) {
    173             var p = [],
    174                 d = [],
    175                 r0;
    176
    177             p = this.point.coords;
    178             d = this.vec;
    179
    180             // Intersect the ray - if necessary - with the cube,
    181             // i.e. clamp the line.
    182             r0 = Type.evaluate(r);
    183             r = this.view.intersectionLineCube(p, d, r0);
    184
    185             return [
    186                 p[0] + d[0] * r,
    187                 p[1] + d[1] * r,
    188                 p[2] + d[2] * r,
    189                 p[3] + d[3] * r
    190             ];
    191         },
    192
    193         addTransform: function (el, transform) {
    194             this.point.addTransform(el.point, transform);
    195             this.addTransformGeneric(el, transform);
    196
    197             return this;
    198         },
    199
    200         updateTransform: function () {
    201             var c, i;
    202
    203             if (this.transformations.length === 0 || this.baseElement === null) {
    204                 return this;
    205             }
    206
    207             if (this === this.baseElement) {
    208                 c = this.vec;
    209             } else {
    210                 c = this.baseElement.vec;
    211             }
    212             for (i = 0; i < this.transformations.length; i++) {
    213                 this.transformations[i].update();
    214                 c = Mat.matVecMult(this.transformations[i].matrix, c);
    215             }
    216             this.vec = c;
    217
    218             return this;
    219         },
    220
    221         // Already documented in JXG.GeometryElement
    222         update: function () {
    223             if (this.needsUpdate) {
    224                 this.updateCoords()
    225                     .updateTransform();
    226             }
    227             return this;
    228         },
    229
    230         /**
    231          * Set the 2D position of the defining points.
    232          *
    233          * @name Line3D#setPosition2D
    234          * @function
    235          * @param {JXG.Transformation} t projective 2D transformation
    236          * @private
    237          */
    238         setPosition2D: function (t) {
    239             var j, el;
    240
    241             for (j = 0; j < this.parents.length; j++) {
    242                 // Run through defining 3D points
    243                 el = this.view.select(this.parents[j]);
    244                 if (el.elType === 'point3d' && el.element2D.draggable()) {
    245                     t.applyOnce(el.element2D);
    246                 }
    247             }
    248             this.endpoints[0].update();
    249             this.endpoints[1].update();
    250         },
    251
    252         // Already documented in JXG.GeometryElement
    253         updateRenderer: function () {
    254             this.needsUpdate = false;
    255             return this;
    256         },
    257
    258         // Already documented in element3d.js
    259         projectCoords: function (p, params) {
    260             var p0_coords = this.getPointCoords(0),
    261                 p1_coords = this.getPointCoords(1),
    262                 dir = [
    263                     p1_coords[0] - p0_coords[0],
    264                     p1_coords[1] - p0_coords[1],
    265                     p1_coords[2] - p0_coords[2]
    266                 ],
    267                 diff = [
    268                     p[0] - p0_coords[0],
    269                     p[1] - p0_coords[1],
    270                     p[2] - p0_coords[2]
    271                 ],
    272                 t = Mat.innerProduct(diff, dir) / Mat.innerProduct(dir, dir),
    273                 t_clamped = Math.min(Math.max(t, Type.evaluate(this.range[0])), Type.evaluate(this.range[1])),
    274                 c3d;
    275
    276             c3d = this.getPointCoords(t_clamped).slice();
    277             params[0] = t_clamped;
    278
    279             return c3d;
    280         },
    281
    282         // projectScreenCoords: function (pScr) {
    283         //     var end0 = this.getPointCoords(0),
    284         //         end1 = this.getPointCoords(1);
    285
    286         //     return this.view.projectScreenToSegment(pScr, end0, end1);
    287         // },
    288
    289         /**
    290          * Update the z-index of the line, i.e. the z-index of its midpoint.
    291          * @name Line3D#updateZIndex
    292          * @function
    293          * @returns {Object} Reference to Line3D object
    294          */
    295         updateZIndex: function() {
    296             var p1 = this.endpoints[0],
    297                 p2 = this.endpoints[1],
    298                 c3d = [1, p1.X() + p2.X(), p1.Y() + p2.Y(), p1.Z() + p2.Z()];
    299
    300             c3d[1] *= 0.5;
    301             c3d[2] *= 0.5;
    302             c3d[3] *= 0.5;
    303             this.zIndex = Mat.matVecMult(this.view.matrix3DRotShift, c3d)[3];
    304
    305             return this;
    306         }
    307     }
    308 );
    309
    310 /**
    311  * @class A line in 3D is given by two points, or one point and a direction vector.
    312  *
    313  * @description
    314  * A line in 3D is given by two points, or one point and a direction vector.
    315  * That is, there are the following two possibilities to create a Line3D object:
    316  * <ol>
    317  * <li> The 3D line is defined by two 3D points (Point3D):
    318  * The points can be either existing points or coordinate arrays of
    319  * the form [x, y, z].
    320  * <p> The 3D line is defined by a point (or coordinate array [x, y, z])
    321  * a direction given as array [x, y, z] and an optional range
    322  * given as array [s, e]. The default value for the range is [-Infinity, Infinity].
    323  * </ol>
    324  * All numbers can also be provided as functions returning a number.
    325  * The case [point, array] is ambiguous, it is not clear if 'array' contains the coordinates of a point
    326  * or of a direction. In that case, 'array' is interpreted as the coordinate array of a point,
    327  * i.e. the line is defined by two points.
    328  *
    329  * @pseudo
    330  * @name Line3D
    331  * @augments JXG.GeometryElement3D
    332  * @constructor
    333  * @type JXG.Line3D
    334  * @throws {Exception} If the element cannot be constructed with the given parent
    335  * objects an exception is thrown.
    336  * @param {JXG.Point3D,array,function_JXG.Point3D,array,function} point1,point2 First and second defining point of the line.
    337  * The attributes {@link Line3D#straightFirst} and {@link Line3D#straightLast} control if the line is displayed as
    338  * segment, ray or infinite line.
    339  * @param {JXG.Point3D,array,function_JXG.Line3D,array,function_array,function} point,direction,range The line is defined by point, direction and range.
    340  * <ul>
    341  * <li> point: Point3D or array of length 3
    342  * <li> direction: array of length 3 or function returning an array of numbers or function returning an array
    343  * <li> range: array of length 2, elements can also be functions. Use [-Infinity, Infinity] for infinite lines.
    344  * </ul>
    345  *
    346  * @example
    347  *     var bound = [-5, 5];
    348  *     var view = board.create('view3d',
    349  *         [[-6, -3], [8, 8],
    350  *         [bound, bound, bound]],
    351  *         {});
    352  *     var p = view.create('point3d', [1, 2, 2], { name:'A', size: 5 });
    353  *     // Lines through 2 points
    354  *     var l1 = view.create('line3d', [[1, 3, 3], [-3, -3, -3]], {point1: {visible: true}, point2: {visible: true} });
    355  *     var l2 = view.create('line3d', [p, l1.point1]);
    356  *
    357  *     // Line by point, direction, range
    358  *     var l3 = view.create('line3d', [p, [0, 0, 1], [-2, 4]]);
    359  *
    360  * </pre><div id='JXG05f9baa4-6059-4502-8911-6a934f823b3d' class='jxgbox' style='width: 300px; height: 300px;'></div>
    361  * <script type='text/javascript'>
    362  *     (function() {
    363  *         var board = JXG.JSXGraph.initBoard('JXG05f9baa4-6059-4502-8911-6a934f823b3d',
    364  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
    365  *         var bound = [-5, 5];
    366  *         var view = board.create('view3d',
    367  *             [[-6, -3], [8, 8],
    368  *             [bound, bound, bound]],
    369  *             {});
    370  *         var p = view.create('point3d', [1, 2, 2], { name:'A', size: 5 });
    371  *         // Lines through 2 points
    372  *         var l1 = view.create('line3d', [[1, 3, 3], [-3, -3, -3]], {name: 'll1', point1: {visible: true}, point2: {visible: true} });
    373  *         var l2 = view.create('line3d', [p, l1.point1]);
    374  *         // Line by point, direction, range
    375  *         var l3 = view.create('line3d', [p, [0, 0, 1], [-2, 4]]);
    376  *     })();
    377  *
    378  * </script><pre>
    379  *
    380  * @example
    381  *     var view = board.create(
    382  *         'view3d',
    383  *         [[-6, -3], [8, 8],
    384  *         [[-3, 3], [-3, 3], [-3, 3]]],
    385  *         {
    386  *             depthOrder: {
    387  *                 enabled: true
    388  *             },
    389  *             projection: 'central',
    390  *             xPlaneRear: {fillOpacity: 0.2},
    391  *             yPlaneRear: {fillOpacity: 0.2},
    392  *             zPlaneRear: {fillOpacity: 0.2}
    393  *         }
    394  *     );
    395  *
    396  *     var A = view.create('point3d', [0, 0, 0], {size: 2});
    397  *     var B = view.create('point3d', [2, 1, 1], {size: 2});
    398  *     var C = view.create('point3d', [-2.5, 2.5, 1.5], {size: 2});
    399  *
    400  *     // Draggable line by two points
    401  *     var line1 = view.create('line3d', [A, B], {
    402  *         fixed: false,
    403  *         straightFirst: true,
    404  *         straightLast: true,
    405  *         dash: 2
    406  *     });
    407  *
    408  *     // Line by point, direction, and range
    409  *     var line2 = view.create('line3d', [C, [1, 0, 0], [-1, Infinity]], {
    410  *         strokeColor: 'blue'
    411  *     });
    412  *
    413  *     // Line by point and array
    414  *     var line3 = view.create('line3d', [C, [-2.5, -1, 1.5]], {
    415  *         point2: { visible: true},
    416  *         strokeColor: 'red'
    417  *     });
    418  *
    419  * </pre><div id="JXGc42dda18-0a72-45f2-8add-3b2ad7e10853" class="jxgbox" style="width: 300px; height: 300px;"></div>
    420  * <script type="text/javascript">
    421  *     (function() {
    422  *         var board = JXG.JSXGraph.initBoard('JXGc42dda18-0a72-45f2-8add-3b2ad7e10853',
    423  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
    424  *         var view = board.create(
    425  *             'view3d',
    426  *             [[-6, -3], [8, 8],
    427  *             [[-3, 3], [-3, 3], [-3, 3]]],
    428  *             {
    429  *                 depthOrder: {
    430  *                     enabled: true
    431  *                 },
    432  *                 projection: 'central',
    433  *                 xPlaneRear: {fillOpacity: 0.2},
    434  *                 yPlaneRear: {fillOpacity: 0.2},
    435  *                 zPlaneRear: {fillOpacity: 0.2}
    436  *             }
    437  *         );
    438  *
    439  *         var A = view.create('point3d', [0, 0, 0], {size: 2});
    440  *         var B = view.create('point3d', [2, 1, 1], {size: 2});
    441  *         var C = view.create('point3d', [-2.5, 2.5, 1.5], {size: 2});
    442  *
    443  *         // Draggable line by two points
    444  *         var line1 = view.create('line3d', [A, B], {
    445  *             fixed: false,
    446  *             straightFirst: true,
    447  *             straightLast: true,
    448  *             dash: 2
    449  *         });
    450  *
    451  *         // Line by point, direction, and range
    452  *         var line2 = view.create('line3d', [C, [1, 0, 0], [-1, Infinity]], {
    453  *             strokeColor: 'blue'
    454  *         });
    455  *
    456  *         // Line by point and array
    457  *         var line3 = view.create('line3d', [C, [-2.5, -1, 1.5]], {
    458  *             point2: { visible: true},
    459  *             strokeColor: 'red'
    460  *         });
    461  *
    462  *     })();
    463  *
    464  * </script><pre>
    465  *
    466  * @example
    467  *  var view = board.create(
    468  *      'view3d',
    469  *      [[-6, -3], [8, 8],
    470  *      [[-3, 3], [-3, 3], [-3, 3]]],
    471  *      {
    472  *          depthOrder: {
    473  *              enabled: true
    474  *          },
    475  *          projection: 'parallel',
    476  *          xPlaneRear: { fillOpacity: 0.2 },
    477  *          yPlaneRear: { fillOpacity: 0.2 },
    478  *          zPlaneRear: { fillOpacity: 0.2 }
    479  *      }
    480  *  );
    481  *
    482  *
    483  * var A = view.create('point3d', [-2, 0, 1], { size: 2 });
    484  * var B = view.create('point3d', [-2, 0, 2], { size: 2 });
    485  * var line1 = view.create('line3d', [A, B], {
    486  *     fixed: false,
    487  *     strokeColor: 'blue',
    488  *     straightFirst: true,
    489  *     straightLast: true
    490  * });
    491  *
    492  * var C = view.create('point3d', [2, 0, 1], { size: 2 });
    493  * var line2 = view.create('line3d', [C, line1, [-Infinity, Infinity]], { strokeColor: 'red' });
    494  *
    495  * </pre><div id="JXGc9234445-de9b-4543-aae7-0ef2d0b540e6" class="jxgbox" style="width: 300px; height: 300px;"></div>
    496  * <script type="text/javascript">
    497  *     (function() {
    498  *         var board = JXG.JSXGraph.initBoard('JXGc9234445-de9b-4543-aae7-0ef2d0b540e6',
    499  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
    500  *                 var view = board.create(
    501  *                     'view3d',
    502  *                     [[-6, -3], [8, 8],
    503  *                     [[-3, 3], [-3, 3], [-3, 3]]],
    504  *                     {
    505  *                         depthOrder: {
    506  *                             enabled: true
    507  *                         },
    508  *                         projection: 'parallel',
    509  *                         xPlaneRear: { fillOpacity: 0.2 },
    510  *                         yPlaneRear: { fillOpacity: 0.2 },
    511  *                         zPlaneRear: { fillOpacity: 0.2 }
    512  *                     }
    513  *                 );
    514  *
    515  *
    516  *                 var A = view.create('point3d', [-2, 0, 1], { size: 2 });
    517  *                 var B = view.create('point3d', [-2, 0, 2], { size: 2 });
    518  *                 var line1 = view.create('line3d', [A, B], {
    519  *                     fixed: false,
    520  *                     strokeColor: 'blue',
    521  *                     straightFirst: true,
    522  *                     straightLast: true
    523  *                 });
    524  *
    525  *                 var C = view.create('point3d', [2, 0, 1], { size: 2 });
    526  *                 var line2 = view.create('line3d', [C, line1, [-Infinity, Infinity]], { strokeColor: 'red' });
    527  *
    528  *     })();
    529  *
    530  * </script><pre>
    531  *
    532  */
    533 JXG.createLine3D = function (board, parents, attributes) {
    534     var view = parents[0],
    535         attr, points,
    536         point, direction, range,
    537         point1, point2, endpoints,
    538         el,
    539         base = null,
    540         transform = null;
    541
    542     attr = Type.copyAttributes(attributes, board.options, 'line3d');
    543
    544     // In any case, parents[1] contains a point or point coordinates
    545
    546     if (parents[1].type === Const.OBJECT_TYPE_LINE3D &&
    547         Type.isTransformationOrArray(parents[2])
    548     ) {
    549         base = parents[1];
    550         transform = parents[2];
    551
    552         points = Type.providePoints3D(
    553             view,
    554             [
    555                 [0, 0, 0, 0],
    556                 [0, 0, 0, 0]
    557             ],
    558             attributes,
    559             'line3d',
    560             ['point1', 'point2']
    561         );
    562     }
    563
    564     if (base === null &&   // No transformation
    565         (Type.isPoint3D(parents[2]) ||
    566             ( parents.length === 3 && (Type.isArray(parents[2]) || Type.isFunction(parents[2])) )
    567         )
    568     ) {
    569         // Line defined by two points; [view, point1, point2]
    570         point1 = Type.providePoints3D(view, [parents[1]], attributes, 'line3d', ['point1'])[0];
    571         point2 = Type.providePoints3D(view, [parents[2]], attributes, 'line3d', ['point2'])[0];
    572         direction = function () {
    573             return [0, point2.X() - point1.X(), point2.Y() - point1.Y(), point2.Z() - point1.Z()];
    574         };
    575         range = [0, 1]; // Segment by default
    576         el = new JXG.Line3D(view, point1, direction, range, attr);
    577         el.prepareUpdate().update();
    578
    579         // Create two shadow points that are the end points of the visible line.
    580         // This is of relevance if the line has straightFirst or straightLast set to true, then
    581         // endpoints differ from point1, point2.
    582         // In such a case, the endpoints are the intersection of the line with the cube.
    583         endpoints = Type.providePoints3D(
    584             view,
    585             [
    586                 [1, 0, 0, 0],
    587                 [1, 0, 0, 0]
    588             ],
    589             { visible: true },
    590             'line3d',
    591             ['point1', 'point2']
    592         );
    593
    594         /**
    595          * @class
    596          * @ignore
    597          */
    598         endpoints[0].F = function () {
    599             var r = 0;
    600             if (el.evalVisProp('straightfirst')) {
    601                 r = -Infinity;
    602             }
    603             return el.getPointCoords(r);
    604         };
    605
    606         /**
    607          * @class
    608          * @ignore
    609          */
    610         endpoints[1].F = function () {
    611             var r = 1;
    612             if (el.evalVisProp('straightlast')) {
    613                 r = Infinity;
    614             }
    615             return el.getPointCoords(r);
    616         };
    617         endpoints[0].prepareUpdate().update();
    618         endpoints[1].prepareUpdate().update();
    619
    620         // The 2D line is always a segment.
    621         attr = el.setAttr2D(attr);
    622         attr.straightfirst = false;
    623         attr.straightlast = false;
    624         el.element2D = view.create('segment', [endpoints[0].element2D, endpoints[1].element2D], attr);
    625         el.element2D.view = view;
    626
    627         /**
    628          * Shadow points that determine the visible line.
    629          * This is of relevance if the line is defined by two points and has straightFirst or straightLast set to true.
    630          * In such a case, the shadow points are the intersection of the line with the cube.
    631          *
    632          * @name JXG.Point3D.endpoints
    633          * @type Array
    634          * @private
    635          */
    636         el.endpoints = endpoints;
    637         el.addChild(endpoints[0]);
    638         el.addChild(endpoints[1]);
    639         // el.setParents(endpoints);
    640         el.addParents([point1, point2]);
    641
    642     } else {
    643         // Line defined by point, direction and range
    644
    645
    646         // Directions are handled as arrays of length 4, i.e. with homogeneous coordinates.
    647         if (base !== null) {
    648             point = Type.providePoints3D(view, [[0, 0, 0]], attributes, 'line3d', ['point'])[0];
    649             direction = [0, 0, 0, 0.0001];
    650             range = parents[3] || [-Infinity, Infinity];
    651         } else if (
    652             (Type.exists(parents[2].view) && parents[2].type === Const.OBJECT_TYPE_LINE3D) || // direction given by another line
    653             Type.isFunction(parents[2]) || (parents[2].length === 3) || (parents[2].length === 4) // direction given as function or array
    654         ) {
    655             point = Type.providePoints3D(view, [parents[1]], attributes, 'line3d', ['point'])[0];
    656             direction = parents[2];
    657             range = parents[3];
    658         } else {
    659             throw new Error(
    660                 "JSXGraph: Can't create line3d with parents of type '" +
    661                     typeof parents[1] + ", "  +
    662                     typeof parents[2] + ", "  +
    663                     typeof parents[3] + "'."
    664             );
    665         }
    666
    667         points = Type.providePoints3D(
    668             view,
    669             [
    670                 [1, 0, 0, 0],
    671                 [1, 0, 0, 0]
    672             ],
    673             attributes,
    674             'line3d',
    675             ['point1', 'point2']
    676         );
    677
    678         // Create a line3d with two dummy points
    679         el = new JXG.Line3D(view, point, direction, range, attr);
    680         el.prepareUpdate().update();
    681
    682         // Now set the real points which define the line
    683         /**
    684          * @class
    685          * @ignore
    686          */
    687         points[0].F = function () {
    688             return el.getPointCoords(Type.evaluate(el.range[0]));
    689         };
    690         points[0].prepareUpdate().update();
    691         point1 = points[0];
    692
    693         /**
    694          * @class
    695          * @ignore
    696          */
    697         points[1].F = function () {
    698             return el.getPointCoords(Type.evaluate(el.range[1]));
    699         };
    700         points[1].prepareUpdate().update();
    701         point2 = points[1];
    702
    703         attr = el.setAttr2D(attr);
    704         attr.straightfirst = false;
    705         attr.straightlast = false;
    706         el.element2D = view.create('segment', [point1.element2D, point2.element2D], attr);
    707         el.element2D.view = view;
    708
    709         /**
    710          * Array of length 2 containing the endings of the Line3D element. These are the defining points,
    711          * the intersections of the line with the bounding box, or the endings defined by the range.
    712          * @name Line3D#endpoints
    713          * @type {Array}
    714          */
    715         el.endpoints = points;
    716
    717         el.addParents(point);
    718
    719         if (base !== null && transform !== null) {
    720             el.addTransform(base, transform);
    721             el.addParents(base);
    722         }
    723     }
    724
    725     el.addChild(el.element2D);
    726     el.inherits.push(el.element2D);
    727     el.element2D.addParents(el);
    728
    729     el.point1 = point1;
    730     el.point2 = point2;
    731     if (el.point1._is_new) {
    732         el.addChild(el.point1);
    733         delete el.point1._is_new;
    734     } else {
    735         el.point1.addChild(el);
    736     }
    737     if (el.point2._is_new) {
    738         el.addChild(el.point2);
    739         delete el.point2._is_new;
    740     } else {
    741         el.point2.addChild(el);
    742     }
    743     if (Type.exists(point)) {
    744         if (point._is_new) {
    745             el.addChild(point);
    746             delete point._is_new;
    747         } else {
    748             point.addChild(el);
    749         }
    750     }
    751
    752     el.update();
    753     el.element2D.prepareUpdate().update().updateRenderer();
    754     return el;
    755 };
    756
    757 JXG.registerElement('line3d', JXG.createLine3D);
    758
    759 // -----------------------
    760 //  Planes
    761 // -----------------------
    762
    763 /**
    764  * Constructor for 3D planes.
    765  * @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.
    766  *
    767  * @augments JXG.GeometryElement3D
    768  * @augments JXG.GeometryElement
    769  * @param {View3D} view
    770  * @param {Point3D|Array} point
    771  * @param {Array} direction1
    772  * @param {Array} range_u
    773  * @param {Array} direction2
    774  * @param {Array} range_v
    775  * @param {Object} attributes
    776  * @see JXG.Board#generateName
    777  */
    778 JXG.Plane3D = function (view, point, dir1, range_u, dir2, range_v, attributes) {
    779     this.constructor(view.board, attributes, Const.OBJECT_TYPE_PLANE3D, Const.OBJECT_CLASS_3D);
    780     this.constructor3D(view, 'plane3d');
    781
    782     this.board.finalizeAdding(this);
    783
    784     /**
    785      * 3D point which - together with two direction vectors - defines the plane.
    786      *
    787      * @name point
    788      * @memberOf Plane3D
    789      * @type JXG.Point3D
    790      *
    791      * @see Plane3D#direction1
    792      * @see Plane3D#direction2
    793      */
    794     this.point = point;
    795
    796     /**
    797      * Two linearly independent vectors - together with a point - define the plane. Each of these direction vectors is an
    798      * array of numbers or functions (either of length 3 or 4) or function returning array of length 3 or 4.
    799      * Homogeneous coordinates of directions have the form [0, x, y, z].
    800      *
    801      * @name Plane3D#direction1
    802      * @type Array|Function
    803      *
    804      * @see Plane3D.point
    805      * @see Plane3D#direction2
    806      */
    807     this.direction1 = dir1;
    808
    809     /**
    810      * Two linearly independent vectors - together with a point - define the plane. Each of these direction vectors is an
    811      * array of numbers or functions (either of length 3 or 4) or function returning array of length 3 or 4.
    812      * Homogeneous coordinates of directions have the form [0, x, y, z].
    813      *
    814      * @type Array|Function
    815      * @name Plane3D#direction2
    816      * @see Plane3D.point
    817      * @see Plane3D#direction1
    818      */
    819     this.direction2 = dir2;
    820
    821     /**
    822      * Range [r1, r2] of {@link direction1}. The 3D line goes from (point + r1 * direction1) to (point + r2 * direction1)
    823      * @name Plane3D#range_u
    824      * @type {Array}
    825      * @default [-Infinity, Infinity]
    826      * @default
    827      */
    828     this.range_u = range_u || [-Infinity, Infinity];
    829
    830     /**
    831      * Range [r1, r2] of {@link direction2}. The 3D line goes from (point + r1 * direction2) to (point + r2 * direction2)
    832      * @name Plane3D#range_v
    833      * @type {Array}
    834      * @type {Array}
    835      * @default [-Infinity, Infinity]
    836      */
    837     this.range_v = range_v || [-Infinity, Infinity];
    838
    839     /**
    840      * Spanning vector 1 of the 3D plane. Contains the evaluated coordinates from {@link direction1} and {@link range1}.
    841      * and is of length 4, the first entry being 0, i.e. homogenous coordinates.
    842      *
    843      * @name Plane3D#vec1
    844      * @type Array
    845      * @private
    846      *
    847      * @see Plane3D#updateCoords
    848      */
    849     this.vec1 = [0, 0, 0, 0];
    850
    851     /**
    852      * Spanning vector 2 of the 3D plane. Contains the evaluated coordinates from {@link Plane3D#direction2} and {@link Plane3D#range2}
    853      * and is of length 4, the first entry being 0, i.e. homogenous coordinates.
    854      *
    855      * @name Plane3D#vec2
    856      * @type Array
    857      * @private
    858      *
    859      * @see Plane3D#updateCoords
    860      */
    861     this.vec2 = [0, 0, 0, 0];
    862
    863     /**
    864      * Mesh (grid) element of the plane.
    865      *
    866      * @name Plane3D#mesh3d
    867      * @type Mesh3D
    868      * @private
    869      */
    870     this.mesh3d = null;
    871
    872     /**
    873      * Normal vector of the plane. Left hand side of the Hesse normal form.
    874      * @name Plane3D#normal
    875      * @type Array
    876      * @private
    877      *
    878      * @see Plane3D.updateNormal
    879      *
    880      */
    881     this.normal = [0, 0, 0, 0];
    882
    883     /**
    884      * Right hand side of the Hesse normal form.
    885      * @name Plane3D#d
    886      * @type Array
    887      * @private
    888      *
    889      * @see Plane3D.updateNormal
    890      *
    891      */
    892     this.d = 0;
    893
    894     this.updateCoords();
    895     this.updateNormal();
    896
    897     this.methodMap = Type.deepCopy(this.methodMap, {
    898         // TODO
    899     });
    900 };
    901 JXG.Plane3D.prototype = new JXG.GeometryElement();
    902 Type.copyPrototypeMethods(JXG.Plane3D, JXG.GeometryElement3D, 'constructor3D');
    903
    904 JXG.extend(
    905     JXG.Plane3D.prototype,
    906     /** @lends JXG.Plane3D.prototype */ {
    907
    908         /**
    909          * Get coordinate array [x, y, z] of a point on the plane for parameters (u, v).
    910          *
    911          * @name Plane3D#F
    912          * @function
    913          * @param {Number} u
    914          * @param {Number} v
    915          * @returns Array of length 3.
    916          */
    917         F: function (u, v) {
    918             var i, v1, v2, l1, l2;
    919
    920             v1 = this.vec1.slice();
    921             v2 = this.vec2.slice();
    922             l1 = Mat.norm(v1, 3);
    923             l2 = Mat.norm(v2, 3);
    924             for (i = 0; i < 3; i++) {
    925                 v1[i] /= l1;
    926                 v2[i] /= l2;
    927             }
    928
    929             return [
    930                 this.point.X() + u * v1[0] + v * v2[0],
    931                 this.point.Y() + u * v1[1] + v * v2[1],
    932                 this.point.Z() + u * v1[2] + v * v2[2]
    933             ];
    934         },
    935
    936         /**
    937          * Get x-coordinate of a point on the plane for parameters (u, v).
    938          *
    939          * @name Plane3D#X
    940          * @function
    941          * @param {Number} u
    942          * @param {Number} v
    943          * @returns Number
    944          */
    945         X: function(u, v) {
    946             return this.F(u, v)[0];
    947         },
    948
    949         /**
    950          * Get y-coordinate of a point on the plane for parameters (u, v).
    951          *
    952          * @name Plane3D#Y
    953          * @function
    954          * @param {Number} u
    955          * @param {Number} v
    956          * @returns Number
    957          */
    958         Y: function(u, v) {
    959             return this.F(u, v)[1];
    960         },
    961
    962         /**
    963          * Get z-coordinate of a point on the plane for parameters (u, v).
    964          *
    965          * @name Plane3D#Z
    966          * @function
    967          * @param {Number} u
    968          * @param {Number} v
    969          * @returns Number
    970          */
    971         Z: function(u, v) {
    972             return this.F(u, v)[2];
    973         },
    974
    975         /**
    976          * Update the arrays {@link JXG.Plane3D#vec1} and {@link JXG.Plane3D#vec1} containing the homogeneous coords of the spanning vectors.
    977          *
    978          * @name Plane3D#updateCoords
    979          * @function
    980          * @returns {Object} Reference to Plane3D object
    981          * @private
    982          */
    983         updateCoords: function() {
    984             var i, s;
    985
    986             if (Type.exists(this.direction1.view) && this.direction1.type === Const.OBJECT_TYPE_LINE3D) {
    987                 this.vec1 = this.direction1.vec.slice();
    988             } else if (Type.isFunction(this.direction1)) {
    989                 this.vec1 = Type.evaluate(this.direction1);
    990                 if (this.vec1.length === 3) {
    991                     this.vec1.unshift(0);
    992                 }
    993             } else {
    994                 s = 0;
    995                 if (this.direction1.length === 3) {
    996                     this.vec1[0] = 0;
    997                     s = 1;
    998                 }
    999                 for (i = 0; i < this.direction1.length; i++) {
    1000                     this.vec1[s + i] = Type.evaluate(this.direction1[i]);
    1001                 }
    1002             }
    1003
    1004             if (Type.exists(this.direction2.view) && this.direction2.type === Const.OBJECT_TYPE_LINE3D) {
    1005                 this.vec2 = this.direction2.vec.slice();
    1006             } else if (Type.isFunction(this.direction2)) {
    1007                 this.vec2 = Type.evaluate(this.direction2);
    1008                 if (this.vec2.length === 3) {
    1009                     this.vec2.unshift(0);
    1010                 }
    1011             } else {
    1012                 s = 0;
    1013                 if (this.direction2.length === 3) {
    1014                     this.vec2[0] = 0;
    1015                     s = 1;
    1016                 }
    1017                 for (i = 0; i < this.direction2.length; i++) {
    1018                     this.vec2[s + i] = Type.evaluate(this.direction2[i]);
    1019                 }
    1020             }
    1021
    1022             return this;
    1023         },
    1024
    1025         /**
    1026          * Update the Hesse normal form of the plane, i.e. update normal vector and right hand side.
    1027          * Updates also {@link vec1} and {@link vec2}.
    1028          *
    1029          * @name Plane3D#updateNormal
    1030          * @function
    1031          * @returns {Object} Reference to the Plane3D object
    1032          * @private
    1033          * @example
    1034          *    plane.updateNormal();
    1035          *
    1036          */
    1037         updateNormal: function () {
    1038             var i, len;
    1039
    1040             if (!this.needsUpdate) {
    1041                 // Extraordinary update, conflicts with rotating of box and plane transformations
    1042                 // this.updateCoords();
    1043             }
    1044
    1045             this.normal = Mat.crossProduct(this.vec1.slice(1), this.vec2.slice(1));
    1046
    1047             len = Mat.norm(this.normal);
    1048             if (Math.abs(len) > Mat.eps * Mat.eps) {
    1049                 for (i = 0; i < 3; i++) {
    1050                     this.normal[i] /= len;
    1051                 }
    1052             }
    1053             this.normal.unshift(0);
    1054             this.d = Mat.innerProduct(this.point.coords, this.normal, 4);
    1055
    1056             return this;
    1057         },
    1058
    1059         // Already documented in element3d.js
    1060         updateDataArray: function () {
    1061             var s1, e1, s2, e2, c2d, l1, l2,
    1062                 planes = ['xPlaneRear', 'yPlaneRear', 'zPlaneRear'], // Must be ordered x, y, z
    1063                 points = [],
    1064                 v1 = [0, 0, 0],
    1065                 v2 = [0, 0, 0],
    1066                 q = [0, 0, 0],
    1067                 p = [0, 0, 0],
    1068                 eps = 1.e-12,
    1069                 d, i, j, a, b, first, pos, pos_akt,
    1070                 view = this.view;
    1071
    1072             this.dataX = [];
    1073             this.dataY = [];
    1074
    1075             this.updateNormal();
    1076
    1077             // Infinite plane
    1078             if (
    1079                 this.elType !== 'axisplane3d' &&
    1080                 view.defaultAxes &&
    1081                 Type.evaluate(this.range_u[0]) === -Infinity &&
    1082                 Type.evaluate(this.range_u[1]) === Infinity &&
    1083                 Type.evaluate(this.range_v[0]) === -Infinity &&
    1084                 Type.evaluate(this.range_v[1]) === Infinity
    1085             ) {
    1086                 // Determine the intersections of the new plane with
    1087                 // the view bbox3d.
    1088                 //
    1089                 // Start with the rear plane.
    1090                 // For each face of the bbox3d we determine two points
    1091                 // which are the end points of the intersecting line
    1092                 // between the plane and a face of bbox3d.
    1093                 // We start with the three rear planes (set in planes[] above)
    1094                 for (j = 0; j < planes.length; j++) {
    1095                     p = view.intersectionPlanePlane(this, view.defaultAxes[planes[j]]);
    1096                     if (p[0] !== false && p[1] !== false) {
    1097                         // This test is necessary to filter out intersection lines which are
    1098                         // identical to intersections of axis planes (they would occur twice),
    1099                         // i.e. edges of bbox3d.
    1100                         for (i = 0; i < points.length; i++) {
    1101                             if (
    1102                                 (Geometry.distance(p[0], points[i][0], 4) < eps &&
    1103                                     Geometry.distance(p[1], points[i][1], 4) < eps) ||
    1104                                 (Geometry.distance(p[0], points[i][1], 4) < eps &&
    1105                                     Geometry.distance(p[1], points[i][0], 4) < eps)
    1106                             ) {
    1107                                 break;
    1108                             }
    1109                         }
    1110                         if (i === points.length) {
    1111                             points.push(p.slice());
    1112                         }
    1113                     }
    1114
    1115                     // Take a point on the corresponding front plane of bbox3d.
    1116                     p = [1, 0, 0, 0];
    1117                     p[j + 1] = view.bbox3D[j][1];
    1118
    1119                     // Use the Hesse normal form of front plane to intersect it with the plane
    1120                     // d is the rhs of the Hesse normal form of the front plane.
    1121                     d = Mat.innerProduct(p, view.defaultAxes[planes[j]].normal, 4);
    1122                     p = view.intersectionPlanePlane(this, view.defaultAxes[planes[j]], d);
    1123
    1124                     if (p[0] !== false && p[1] !== false) {
    1125                         // Do the same test as above
    1126                         for (i = 0; i < points.length; i++) {
    1127                             // Same test for edges of bbox3d as above
    1128                             if (
    1129                                 (Geometry.distance(p[0], points[i][0], 4) < eps &&
    1130                                     Geometry.distance(p[1], points[i][1], 4) < eps) ||
    1131                                 (Geometry.distance(p[0], points[i][1], 4) < eps &&
    1132                                     Geometry.distance(p[1], points[i][0], 4) < eps)
    1133                             ) {
    1134                                 break;
    1135                             }
    1136                         }
    1137                         if (i === points.length) {
    1138                             points.push(p.slice());
    1139                         }
    1140                     }
    1141                 }
    1142
    1143                 // Handle the case that the plane does not intersect bbox3d at all.
    1144                 if (points.length === 0) {
    1145                     return { X: this.dataX, Y: this.dataY };
    1146                 }
    1147
    1148                 // Concatenate the intersection points to a polygon.
    1149                 // If all went well, each intersection should appear
    1150                 // twice in the list.
    1151                 first = 0;
    1152                 pos = first;
    1153                 i = 0;
    1154                 do {
    1155                     p = points[pos][i];
    1156                     if (p.length === 4) {
    1157                         c2d = view.project3DTo2D(p);
    1158                         this.dataX.push(c2d[1]);
    1159                         this.dataY.push(c2d[2]);
    1160                     }
    1161                     i = (i + 1) % 2;
    1162                     p = points[pos][i];
    1163
    1164                     pos_akt = pos;
    1165                     for (j = 0; j < points.length; j++) {
    1166                         if (j !== pos && Geometry.distance(p, points[j][0]) < eps) {
    1167                             pos = j;
    1168                             i = 0;
    1169                             break;
    1170                         }
    1171                         if (j !== pos && Geometry.distance(p, points[j][1]) < eps) {
    1172                             pos = j;
    1173                             i = 1;
    1174                             break;
    1175                         }
    1176                     }
    1177                     if (pos === pos_akt) {
    1178                         console.log('Error plane3d update: did not find next', pos);
    1179                         break;
    1180                     }
    1181                 } while (pos !== first);
    1182
    1183                 c2d = view.project3DTo2D(points[first][0]);
    1184                 this.dataX.push(c2d[1]);
    1185                 this.dataY.push(c2d[2]);
    1186             } else {
    1187                 // 3D bounded flat
    1188                 s1 = Type.evaluate(this.range_u[0]);
    1189                 e1 = Type.evaluate(this.range_u[1]);
    1190                 s2 = Type.evaluate(this.range_v[0]);
    1191                 e2 = Type.evaluate(this.range_v[1]);
    1192
    1193                 q = this.point.coords;
    1194                 v1 = this.vec1.slice();
    1195                 v2 = this.vec2.slice();
    1196                 l1 = Mat.norm(v1, 4);
    1197                 l2 = Mat.norm(v2, 4);
    1198                 for (i = 1; i < 4; i++) {
    1199                     v1[i] /= l1;
    1200                     v2[i] /= l2;
    1201                 }
    1202
    1203                 for (j = 0; j < 4; j++) {
    1204                     switch (j) {
    1205                         case 0:
    1206                             a = s1;
    1207                             b = s2;
    1208                             break;
    1209                         case 1:
    1210                             a = e1;
    1211                             b = s2;
    1212                             break;
    1213                         case 2:
    1214                             a = e1;
    1215                             b = e2;
    1216                             break;
    1217                         case 3:
    1218                             a = s1;
    1219                             b = e2;
    1220                     }
    1221                     for (i = 0; i < 4; i++) {
    1222                         p[i] = q[i] + a * v1[i] + b * v2[i];
    1223                     }
    1224                     c2d = view.project3DTo2D(p);
    1225                     this.dataX.push(c2d[1]);
    1226                     this.dataY.push(c2d[2]);
    1227                 }
    1228                 // Close the curve
    1229                 this.dataX.push(this.dataX[0]);
    1230                 this.dataY.push(this.dataY[0]);
    1231             }
    1232             return { X: this.dataX, Y: this.dataY };
    1233         },
    1234
    1235         // Already documented in element3d.js
    1236         addTransform: function (el, transform) {
    1237             this.addTransformGeneric(el, transform);
    1238             this.point.addTransform(el.point, transform);
    1239             return this;
    1240         },
    1241
    1242         // Already documented in element3d.js
    1243         updateTransform: function () {
    1244             var c1, c2, i;
    1245
    1246             if (this.transformations.length === 0 || this.baseElement === null) {
    1247                 return this;
    1248             }
    1249
    1250             if (this === this.baseElement) {
    1251                 c1 = this.vec1;
    1252                 c2 = this.vec2;
    1253             } else {
    1254                 c1 = this.baseElement.vec1;
    1255                 c2 = this.baseElement.vec2;
    1256             }
    1257
    1258             for (i = 0; i < this.transformations.length; i++) {
    1259                 this.transformations[i].update();
    1260                 c1 = Mat.matVecMult(this.transformations[i].matrix, c1);
    1261                 c2 = Mat.matVecMult(this.transformations[i].matrix, c2);
    1262             }
    1263             this.vec1 = c1;
    1264             this.vec2 = c2;
    1265
    1266             return this;
    1267         },
    1268
    1269         // Already documented in element3d.js
    1270         update: function () {
    1271             if (this.needsUpdate) {
    1272                 this.updateCoords()
    1273                     .updateTransform();
    1274             }
    1275             return this;
    1276         },
    1277
    1278         // Already documented in element3d.js
    1279         updateRenderer: function () {
    1280             this.needsUpdate = false;
    1281             return this;
    1282         },
    1283
    1284         // Already documented in element3d.js
    1285         projectCoords: function (p, params) {
    1286             return Geometry.projectCoordsToParametric(p, this, 2, params);
    1287         }
    1288     }
    1289 );
    1290
    1291 /**
    1292  * @class A 3D plane is defined either by a point and two linearly independent vectors, or by three points.
    1293  *
    1294  * @description
    1295  * A 3D plane is defined either by a point and two linearly independent vectors, or by three points.
    1296  * In the first case, the parameters are a 3D point (or a coordinate array) and two vectors (arrays).
    1297  * In the second case, the parameters consist of three 3D points (given as points or coordinate arrays).
    1298  * In order to distinguish the two cases, in the latter case (three points), the additional attribute {@link Plane3D#threePoints}
    1299  * has to be supplied if both, the second point and the third point, are given as arrays or functions. Otherwise, it would not be
    1300  * clear if the input arrays have to be interpreted as points or directions.
    1301  * <p>
    1302  * All coordinate arrays can be supplied as functions returning a coordinate array.
    1303  *
    1304  * @pseudo
    1305  * @name  Plane3D
    1306  * @augments JXG.GeometryElement3D
    1307  * @constructor
    1308  * @throws {Exception} If the element cannot be constructed with the given parent
    1309  * objects an exception is thrown.
    1310  *
    1311  * @param {JXG.Point3D,array,function_JXG.Line3D,array,function_JXG.Line3D,array,function_array,function_array,function} point,direction1,direction2,[range1],[range2] The plane is defined by point, direction1, direction2, range1, and range2.
    1312  * <ul>
    1313  * <li> point: Point3D or array of length 3
    1314  * <li> direction1: line3d element or array of length 3 or function returning an array of numbers or function returning an array
    1315  * <li> direction2: line3d element or array of length 3 or function returning an array of numbers or function returning an array
    1316  * <li> range1: array of length 2, elements can also be functions. Use [-Infinity, Infinity] for infinite lines.
    1317  * <li> range2: array of length 2, elements can also be functions. Use [-Infinity, Infinity] for infinite lines.
    1318  * </ul>
    1319  * @param {JXG.Point3D,array,function_JXG.Point3D,array,function_JXG.Point3D,array,function} point1,point2,point3 The plane is defined by three points.
    1320  * @type JXG.Plane3D
    1321  *
    1322  * @example
    1323  *     var view = board.create(
    1324  *         'view3d',
    1325  *         [[-6, -3], [8, 8],
    1326  *         [[-3, 3], [-3, 3], [-3, 3]]],
    1327  *         {
    1328  *             depthOrder: {
    1329  *                 enabled: true
    1330  *             },
    1331  *             projection: 'central',
    1332  *             xPlaneRear: {fillOpacity: 0.2},
    1333  *             yPlaneRear: {fillOpacity: 0.2},
    1334  *             zPlaneRear: {fillOpacity: 0.2}
    1335  *         }
    1336  *     );
    1337  *
    1338  *     var A = view.create('point3d', [-2, 0, 1], {size: 2});
    1339  *
    1340  *     // Infinite Plane by point and two directions
    1341  *     var plane = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0], [-Infinity, Infinity], [-Infinity, Infinity]]);
    1342  *
    1343  * </pre><div id="JXG69f491ef-d7c7-4105-a962-86a588fbd23b" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1344  * <script type="text/javascript">
    1345  *     (function() {
    1346  *         var board = JXG.JSXGraph.initBoard('JXG69f491ef-d7c7-4105-a962-86a588fbd23b',
    1347  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
    1348  *         var view = board.create(
    1349  *             'view3d',
    1350  *             [[-6, -3], [8, 8],
    1351  *             [[-3, 3], [-3, 3], [-3, 3]]],
    1352  *             {
    1353  *                 depthOrder: {
    1354  *                     enabled: true
    1355  *                 },
    1356  *                 projection: 'central',
    1357  *                 xPlaneRear: {fillOpacity: 0.2},
    1358  *                 yPlaneRear: {fillOpacity: 0.2},
    1359  *                 zPlaneRear: {fillOpacity: 0.2}
    1360  *             }
    1361  *         );
    1362  *
    1363  *         var A = view.create('point3d', [-2, 0, 1], {size: 2});
    1364  *
    1365  *         // Infinite Plane by point and two directions
    1366  *         var plane = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0], [-Infinity, Infinity], [-Infinity, Infinity]]);
    1367  *
    1368  *     })();
    1369  *
    1370  * </script><pre>
    1371  *
    1372  * @example
    1373  *     var view = board.create(
    1374  *         'view3d',
    1375  *         [[-6, -3], [8, 8],
    1376  *         [[-3, 3], [-3, 3], [-3, 3]]],
    1377  *         {
    1378  *             depthOrder: {
    1379  *                 enabled: true
    1380  *             },
    1381  *             projection: 'central',
    1382  *             xPlaneRear: {fillOpacity: 0.2},
    1383  *             yPlaneRear: {fillOpacity: 0.2},
    1384  *             zPlaneRear: {fillOpacity: 0.2}
    1385  *         }
    1386  *     );
    1387  *
    1388  *     var A = view.create('point3d', [-2, 0, 1], {size: 2});
    1389  *
    1390  *     // Finite Plane by point and two directions
    1391  *     var plane1 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0], [-2, 2], [-2, 2]]);
    1392  *     var plane2 = view.create('plane3d', [[0, 0, -1], [1, 0, 0], [0, 1, 0], [-2, 2], [-2, 2]], {
    1393  *         mesh3d: { visible: true },
    1394  *         point: {visible: true, name: "B", fixed: false}
    1395  *     });
    1396  *
    1397  * </pre><div id="JXGea9dda1b-748b-4ed3-b4b3-57e310bd8141" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1398  * <script type="text/javascript">
    1399  *     (function() {
    1400  *         var board = JXG.JSXGraph.initBoard('JXGea9dda1b-748b-4ed3-b4b3-57e310bd8141',
    1401  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
    1402  *         var view = board.create(
    1403  *             'view3d',
    1404  *             [[-6, -3], [8, 8],
    1405  *             [[-3, 3], [-3, 3], [-3, 3]]],
    1406  *             {
    1407  *                 depthOrder: {
    1408  *                     enabled: true
    1409  *                 },
    1410  *                 projection: 'central',
    1411  *                 xPlaneRear: {fillOpacity: 0.2},
    1412  *                 yPlaneRear: {fillOpacity: 0.2},
    1413  *                 zPlaneRear: {fillOpacity: 0.2}
    1414  *             }
    1415  *         );
    1416  *
    1417  *         var A = view.create('point3d', [-2, 0, 1], {size: 2});
    1418  *
    1419  *         // Finite Plane by point and two directions
    1420  *         var plane1 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0], [-2, 2], [-2, 2]]);
    1421  *         var plane2 = view.create('plane3d', [[0, 0, -1], [1, 0, 0], [0, 1, 0], [-2, 2], [-2, 2]], {
    1422  *             mesh3d: { visible: true },
    1423  *             point: {visible: true, name: "B", fixed: false}
    1424  *         });
    1425  *
    1426  *     })();
    1427  *
    1428  * </script><pre>
    1429  * @example
    1430  *             var view = board.create(
    1431  *                 'view3d',
    1432  *                 [[-6, -3], [8, 8],
    1433  *                 [[-3, 3], [-3, 3], [-3, 3]]],
    1434  *                 {
    1435  *                     depthOrder: {
    1436  *                         enabled: true
    1437  *                     },
    1438  *                     projection: 'central',
    1439  *                     xPlaneRear: { visible: false, fillOpacity: 0.2 },
    1440  *                     yPlaneRear: { visible: false, fillOpacity: 0.2 },
    1441  *                     zPlaneRear: { fillOpacity: 0.2 }
    1442  *                 }
    1443  *             );
    1444  *
    1445  *             var A = view.create('point3d', [-2, 0, 1], { size: 2 });
    1446  *
    1447  *             var line1 = view.create('line3d', [A, [0, 0, 1], [-Infinity, Infinity]], { strokeColor: 'blue' });
    1448  *             var line2 = view.create('line3d', [A, [1, 1, 0], [-Infinity, Infinity]], { strokeColor: 'blue' });
    1449  *
    1450  *             // Plane by point and two lines
    1451  *             var plane2 = view.create('plane3d', [A, line1, line2], {
    1452  *                 fillColor: 'blue'
    1453  *             });
    1454  *
    1455  * </pre><div id="JXG8bc6e266-e27c-4ffa-86a2-8076f4069573" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1456  * <script type="text/javascript">
    1457  *     (function() {
    1458  *         var board = JXG.JSXGraph.initBoard('JXG8bc6e266-e27c-4ffa-86a2-8076f4069573',
    1459  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
    1460  *                 var view = board.create(
    1461  *                     'view3d',
    1462  *                     [[-6, -3], [8, 8],
    1463  *                     [[-3, 3], [-3, 3], [-3, 3]]],
    1464  *                     {
    1465  *                         depthOrder: {
    1466  *                             enabled: true
    1467  *                         },
    1468  *                         projection: 'central',
    1469  *                         xPlaneRear: { visible: false, fillOpacity: 0.2 },
    1470  *                         yPlaneRear: { visible: false, fillOpacity: 0.2 },
    1471  *                         zPlaneRear: { fillOpacity: 0.2 }
    1472  *                     }
    1473  *                 );
    1474  *
    1475  *                 var A = view.create('point3d', [-2, 0, 1], { size: 2 });
    1476  *
    1477  *                 var line1 = view.create('line3d', [A, [0, 0, 1], [-Infinity, Infinity]], { strokeColor: 'blue' });
    1478  *                 var line2 = view.create('line3d', [A, [1, 1, 0], [-Infinity, Infinity]], { strokeColor: 'blue' });
    1479  *
    1480  *                 // Plane by point and two lines
    1481  *                 var plane2 = view.create('plane3d', [A, line1, line2], {
    1482  *                     fillColor: 'blue'
    1483  *                 });
    1484  *
    1485  *     })();
    1486  *
    1487  * </script><pre>
    1488  *
    1489  * @example
    1490  *     var view = board.create(
    1491  *         'view3d',
    1492  *         [[-6, -3], [8, 8],
    1493  *         [[-3, 3], [-3, 3], [-3, 3]]],
    1494  *         {
    1495  *             depthOrder: {
    1496  *                 enabled: true
    1497  *             },
    1498  *             projection: 'central',
    1499  *             xPlaneRear: {fillOpacity: 0.2},
    1500  *             yPlaneRear: {fillOpacity: 0.2},
    1501  *             zPlaneRear: {fillOpacity: 0.2}
    1502  *         }
    1503  *     );
    1504  *
    1505  *     var A = view.create('point3d', [0, 0, 1], {size: 2});
    1506  *     var B = view.create('point3d', [2, 2, 1], {size: 2});
    1507  *     var C = view.create('point3d', [-2, 0, 1], {size: 2});
    1508  *
    1509  *     // Plane by three points
    1510  *     var plane = view.create('plane3d', [A, B, C], {
    1511  *         fillColor: 'blue'
    1512  *     });
    1513  *
    1514  * </pre><div id="JXG139100df-3ece-4cd1-b34f-28b5b3105106" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1515  * <script type="text/javascript">
    1516  *     (function() {
    1517  *         var board = JXG.JSXGraph.initBoard('JXG139100df-3ece-4cd1-b34f-28b5b3105106',
    1518  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
    1519  *         var view = board.create(
    1520  *             'view3d',
    1521  *             [[-6, -3], [8, 8],
    1522  *             [[-3, 3], [-3, 3], [-3, 3]]],
    1523  *             {
    1524  *                 depthOrder: {
    1525  *                     enabled: true
    1526  *                 },
    1527  *                 projection: 'central',
    1528  *                 xPlaneRear: {fillOpacity: 0.2},
    1529  *                 yPlaneRear: {fillOpacity: 0.2},
    1530  *                 zPlaneRear: {fillOpacity: 0.2}
    1531  *             }
    1532  *         );
    1533  *
    1534  *         var A = view.create('point3d', [0, 0, 1], {size: 2});
    1535  *         var B = view.create('point3d', [2, 2, 1], {size: 2});
    1536  *         var C = view.create('point3d', [-2, 0, 1], {size: 2});
    1537  *
    1538  *         // Plane by three points
    1539  *         var plane = view.create('plane3d', [A, B, C], {
    1540  *             fillColor: 'blue'
    1541  *         });
    1542  *
    1543  *     })();
    1544  *
    1545  * </script><pre>
    1546  *
    1547  * @example
    1548  *     var view = board.create(
    1549  *         'view3d',
    1550  *         [[-6, -3], [8, 8],
    1551  *         [[-3, 3], [-3, 3], [-3, 3]]],
    1552  *         {
    1553  *             depthOrder: {
    1554  *                 enabled: true
    1555  *             },
    1556  *             projection: 'central',
    1557  *             xPlaneRear: {fillOpacity: 0.2},
    1558  *             yPlaneRear: {fillOpacity: 0.2},
    1559  *             zPlaneRear: {fillOpacity: 0.2}
    1560  *         }
    1561  *     );
    1562  *
    1563  *     var A = view.create('point3d', [-2, 0, 1], {size: 2});
    1564  *
    1565  *     // Infinite Plane by two directions,
    1566  *     // range1 = range2 = [-Infinity, Infinity]
    1567  *     var plane1 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0]], {
    1568  *         fillColor: 'blue',
    1569  *     });
    1570  *
    1571  *     // Infinite Plane by three points,
    1572  *     var plane2 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0]], {
    1573  *         threePoints: true,
    1574  *         fillColor: 'red',
    1575  *         point2: {visible: true},
    1576  *         point3: {visible: true}
    1577  *     });
    1578  *
    1579  * </pre><div id="JXGf31b9666-0c2e-45e7-a186-ae2c07b6bdb8" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1580  * <script type="text/javascript">
    1581  *     (function() {
    1582  *         var board = JXG.JSXGraph.initBoard('JXGf31b9666-0c2e-45e7-a186-ae2c07b6bdb8',
    1583  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
    1584  *         var view = board.create(
    1585  *             'view3d',
    1586  *             [[-6, -3], [8, 8],
    1587  *             [[-3, 3], [-3, 3], [-3, 3]]],
    1588  *             {
    1589  *                 depthOrder: {
    1590  *                     enabled: true
    1591  *                 },
    1592  *                 projection: 'central',
    1593  *                 xPlaneRear: {fillOpacity: 0.2},
    1594  *                 yPlaneRear: {fillOpacity: 0.2},
    1595  *                 zPlaneRear: {fillOpacity: 0.2}
    1596  *             }
    1597  *         );
    1598  *
    1599  *         var A = view.create('point3d', [-2, 0, 1], {size: 2});
    1600  *
    1601  *         // Infinite Plane by two directions,
    1602  *         // range1 = range2 = [-Infinity, Infinity]
    1603  *         var plane1 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0]], {
    1604  *             fillColor: 'blue',
    1605  *         });
    1606  *
    1607  *         // Infinite Plane by three points,
    1608  *         var plane2 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0]], {
    1609  *             threePoints: true,
    1610  *             fillColor: 'red',
    1611  *             point2: {visible: true},
    1612  *             point3: {visible: true}
    1613  *         });
    1614  *
    1615  *     })();
    1616  *
    1617  * </script><pre>
    1618  *
    1619  */
    1620 JXG.createPlane3D = function (board, parents, attributes) {
    1621     var view = parents[0],
    1622         attr,
    1623         point, point2, point3,
    1624         dir1, dir2, range_u, range_v,
    1625         el, mesh3d,
    1626         base = null,
    1627         transform = null;
    1628
    1629     attr = Type.copyAttributes(attributes, board.options, 'plane3d');
    1630     if (//parents.length === 4 &&
    1631         // ()
    1632         attr.threepoints || Type.isPoint3D(parents[2]) || Type.isPoint3D(parents[3])
    1633     ) {
    1634         // Three points
    1635         point = Type.providePoints3D(view, [parents[1]], attributes, 'plane3d', ['point1'])[0];
    1636         point2 = Type.providePoints3D(view, [parents[2]], attributes, 'plane3d', ['point2'])[0];
    1637         point3 = Type.providePoints3D(view, [parents[3]], attributes, 'plane3d', ['point3'])[0];
    1638         dir1 = function() {
    1639             return [point2.X() - point.X(), point2.Y() - point.Y(), point2.Z() - point.Z()];
    1640         };
    1641         dir2 = function() {
    1642             return [point3.X() - point.X(), point3.Y() - point.Y(), point3.Z() - point.Z()];
    1643         };
    1644         range_u = parents[4] || [-Infinity, Infinity];
    1645         range_v = parents[5] || [-Infinity, Infinity];
    1646     } else {
    1647         if (parents[1].type === Const.OBJECT_TYPE_PLANE3D &&
    1648             Type.isTransformationOrArray(parents[2])
    1649         ) {
    1650             // Plane + transformation
    1651             base = parents[1];
    1652             transform = parents[2];
    1653
    1654             point = Type.providePoints3D(view, [[0, 0, 0, 0]],  attributes,  'plane3d', ['point'])[0];
    1655             dir1 = [0, 0.0001, 0, 0];
    1656             dir2 = [0, 0, 0.0001, 0];
    1657             range_u = parents[3] || [-Infinity, Infinity];
    1658             range_v = parents[4] || [-Infinity, Infinity];
    1659         } else {
    1660             // Point, direction and ranges
    1661             point = Type.providePoints3D(view, [parents[1]], attributes, 'plane3d', ['point'])[0];
    1662             dir1 = parents[2];
    1663             dir2 = parents[3];
    1664             range_u = parents[4] || [-Infinity, Infinity];
    1665             range_v = parents[5] || [-Infinity, Infinity];
    1666         }
    1667         if (point === false) {
    1668             throw new Error(
    1669                 "JSXGraph: Can't create plane3d with first parent of type '" + typeof parents[1] +
    1670                     "'." +
    1671                     "\nPossible first parent types are: point3d, array of length 3, function returning an array of length 3."
    1672             );
    1673         }
    1674         if ((base !== null && parents < 3) || (base === null && parents.length < 4)) {
    1675             throw new Error(
    1676                 "JSXGraph: Can't create plane3d with parents of type '" +
    1677                     typeof parents[1] + ", "  +
    1678                     typeof parents[2] + ", "  +
    1679                     typeof parents[3] + ", "  +
    1680                     typeof parents[4] + ", "  +
    1681                     typeof parents[5] + "'."
    1682             );
    1683         }
    1684     }
    1685
    1686     el = new JXG.Plane3D(view, point, dir1, range_u, dir2, range_v, attr);
    1687     point.addChild(el);
    1688
    1689     attr = el.setAttr2D(attr);
    1690     el.element2D = view.create('curve', [[], []], attr);
    1691     el.element2D.view = view;
    1692
    1693     if (base !== null && transform !== null) {
    1694         el.addTransform(base, transform);
    1695         el.addParents(base);
    1696     }
    1697
    1698     /**
    1699      * @class
    1700      * @ignore
    1701      */
    1702     el.element2D.updateDataArray = function () {
    1703         var ret = el.updateDataArray();
    1704         this.dataX = ret.X;
    1705         this.dataY = ret.Y;
    1706     };
    1707     el.addChild(el.element2D);
    1708     el.inherits.push(el.element2D);
    1709     el.element2D.setParents(el);
    1710
    1711     if (
    1712         Math.abs(el.range_u[0]) !== Infinity &&
    1713         Math.abs(el.range_u[1]) !== Infinity &&
    1714         Math.abs(el.range_v[0]) !== Infinity &&
    1715         Math.abs(el.range_v[1]) !== Infinity
    1716     ) {
    1717         attr = Type.copyAttributes(attr.mesh3d, board.options, 'mesh3d');
    1718         mesh3d = view.create('mesh3d', [
    1719             function () {
    1720                 return point.coords;
    1721             },
    1722             // dir1, dir2, range_u, range_v
    1723             function() { return el.vec1; },
    1724             function() { return el.vec2; },
    1725             el.range_u,
    1726             el.range_v
    1727         ], attr);
    1728         el.mesh3d = mesh3d;
    1729         el.addChild(mesh3d);
    1730         el.inherits.push(mesh3d);           // TODO Does not work
    1731         el.element2D.inherits.push(mesh3d); // Does work - instead
    1732         mesh3d.setParents(el);
    1733         el.mesh3d.view = view;
    1734     }
    1735
    1736     el.element2D.prepareUpdate().update();
    1737     if (!board.isSuspendedUpdate) {
    1738         el.element2D.updateVisibility().updateRenderer();
    1739     }
    1740
    1741     return el;
    1742 };
    1743
    1744 JXG.registerElement('plane3d', JXG.createPlane3D);
    1745
    1746 /**
    1747  * @class The line that is the intersection of two (infinite) plane elements in 3D.
    1748  *
    1749  * @pseudo
    1750  * @name IntersectionLine3D
    1751  * @augments JXG.Line3D
    1752  * @constructor
    1753  * @type JXG.Line3D
    1754  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1755  * @param {JXG.Plane3D_JXG.Plane3D} el1,el2 The result will be the intersection of el1 and el2.
    1756  * @example
    1757  * // Create the intersection line of two planes
    1758  * var view = board.create(
    1759  *     'view3d',
    1760  *     [[-6, -3], [8, 8],
    1761  *     [[-1, 3], [-1, 3], [-1, 3]]],
    1762  *     {
    1763  *         xPlaneRear: {visible:false},
    1764  *         yPlaneRear: {visible:false},
    1765  *         zPlaneRear: {fillOpacity: 0.2, gradient: null}
    1766  *     }
    1767  * );
    1768  * var a = view.create('point3d', [2, 2, 0]);
    1769  *
    1770  * var p1 = view.create(
    1771  *    'plane3d',
    1772  *     [a, [1, 0, 0], [0, 1, 0]],
    1773  *     {fillColor: '#00ff80'}
    1774  * );
    1775  * var p2 = view.create(
    1776  *    'plane3d',
    1777  *     [a, [-2, 1, 1], [1, -2, 1]],
    1778  *     {fillColor: '#ff0000'}
    1779  * );
    1780  *
    1781  * var i = view.create('intersectionline3d', [p1, p2]);
    1782  *
    1783  * </pre><div id="JXGdb931076-b29a-4eff-b97e-4251aaf24943" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1784  * <script type="text/javascript">
    1785  *     (function() {
    1786  *         var board = JXG.JSXGraph.initBoard('JXGdb931076-b29a-4eff-b97e-4251aaf24943',
    1787  *             {boundingbox: [-8, 8, 8,-8], axis: false, pan: {enabled: false}, showcopyright: false, shownavigation: false});
    1788  *         var view = board.create(
    1789  *             'view3d',
    1790  *             [[-6, -3], [8, 8],
    1791  *             [[-1, 3], [-1, 3], [-1, 3]]],
    1792  *             {
    1793  *                 xPlaneRear: {visible:false},
    1794  *                 yPlaneRear: {visible:false},
    1795  *                 zPlaneRear: {fillOpacity: 0.2, gradient: null}
    1796  *             }
    1797  *         );
    1798  *     var a = view.create('point3d', [2, 2, 0]);
    1799  *
    1800  *     var p1 = view.create(
    1801  *        'plane3d',
    1802  *         [a, [1, 0, 0], [0, 1, 0]],
    1803  *         {fillColor: '#00ff80'}
    1804  *     );
    1805  *     var p2 = view.create(
    1806  *        'plane3d',
    1807  *         [a, [-2, 1, 1], [1, -2, 1]],
    1808  *         {fillColor: '#ff0000'}
    1809  *     );
    1810  *
    1811  *     var i = view.create('intersectionline3d', [p1, p2]);
    1812  *
    1813  *     })();
    1814  *
    1815  * </script><pre>
    1816  *
    1817  */
    1818 JXG.createIntersectionLine3D = function (board, parents, attributes) {
    1819     var view = parents[0],
    1820         el1 = parents[1],
    1821         el2 = parents[2],
    1822         ixnLine, i, func,
    1823         attr = Type.copyAttributes(attributes, board.options, "intersectionline3d"),
    1824         pts = [];
    1825
    1826     func = Geometry.intersectionFunction3D(view, el1, el2);
    1827     for (i = 0; i < 2; i++) {
    1828         pts[i] = view.create('point3d', func[i], attr['point' + (i + 1)]);
    1829     }
    1830     ixnLine = view.create('line3d', pts, attr);
    1831
    1832     try {
    1833         el1.addChild(ixnLine);
    1834         el2.addChild(ixnLine);
    1835     } catch (_e) {
    1836         throw new Error(
    1837             "JSXGraph: Can't create 'intersection' with parent types '" +
    1838             typeof parents[1] +
    1839             "' and '" +
    1840             typeof parents[2] +
    1841             "'."
    1842         );
    1843     }
    1844
    1845     ixnLine.type = Const.OBJECT_TYPE_INTERSECTION_LINE3D;
    1846     ixnLine.elType = 'intersectionline3d';
    1847     ixnLine.setParents([el1.id, el2.id]);
    1848
    1849     return ixnLine;
    1850 };
    1851
    1852 JXG.registerElement('intersectionline3d', JXG.createIntersectionLine3D);
    1853