      1 /*
      2     Copyright 2008-2024
      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     this.board.finalizeAdding(this);
     64
     65     /**
     66      * 3D point which - together with a direction - defines the line.
     67      * @type JXG.Point3D
     68      *
     69      * @see JXG.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      * @type Array|Function
     78      * @see JXG.Line3D#point
     79      */
     80     this.direction = direction;
     81
     82     /**
     83      * Range [r1, r2] of the line. r1, r2 can be numbers or functions.
     84      * The 3D line goes from (point + r1 * direction) to (point + r2 * direction)
     85      * @type Array
     86      */
     87     this.range = range || [-Infinity, Infinity];
     88
     89     /**
     90      * Starting point of the 3D line
     91      * @type JXG.Point3D
     92      * @private
     93      */
     94     this.point1 = null;
     95
     96     /**
     97      * End point of the 3D line
     98      * @type JXG.Point3D
     99      * @private
    100      */
    101     this.point2 = null;
    102
    103     this.methodMap = Type.deepCopy(this.methodMap, {
    104         // TODO
    105     });
    106 };
    107 JXG.Line3D.prototype = new JXG.GeometryElement();
    108 Type.copyPrototypeMethods(JXG.Line3D, JXG.GeometryElement3D, 'constructor3D');
    109
    110 JXG.extend(
    111     JXG.Line3D.prototype,
    112     /** @lends JXG.Line3D.prototype */ {
    113
    114         /**
    115          * Determine one end point of a 3D line from point, direction and range.
    116          *
    117          * @param {Number|function} r
    118          * @private
    119          * @returns Array
    120          */
    121         getPointCoords: function (r) {
    122             var p = [],
    123                 d = [],
    124                 i,
    125                 r0;
    126
    127             p = [this.point.X(), this.point.Y(), this.point.Z()];
    128
    129             if (Type.isFunction(this.direction)) {
    130                 d = this.direction();
    131             } else {
    132                 for (i = 1; i < 4; i++) {
    133                     d.push(Type.evaluate(this.direction[i]));
    134                 }
    135             }
    136
    137             // Intersect the ray - if necessary - with the cube,
    138             // i.e. clamp the line.
    139             r0 = Type.evaluate(r);
    140             r = this.view.intersectionLineCube(p, d, r0);
    141
    142             return [p[0] + d[0] * r, p[1] + d[1] * r, p[2] + d[2] * r];
    143         },
    144
    145         update: function () {
    146             return this;
    147         },
    148
    149         setPosition2D: function (t) {
    150             var j, el;
    151
    152             for (j = 0; j < this.parents.length; j++) {
    153                 // Run through defining 3D points
    154                 el = this.view.select(this.parents[j]);
    155                 if (el.elType === 'point3d' && el.element2D.draggable()) {
    156                     t.applyOnce(el.element2D);
    157                 }
    158             }
    159         },
    160
    161         updateRenderer: function () {
    162             this.needsUpdate = false;
    163             return this;
    164         },
    165
    166         projectCoords: function (p) {
    167             var p0_coords = this.getPointCoords(0),
    168                 p1_coords = this.getPointCoords(1),
    169                 dir = [
    170                     p1_coords[0] - p0_coords[0],
    171                     p1_coords[1] - p0_coords[1],
    172                     p1_coords[2] - p0_coords[2]
    173                 ],
    174                 diff = [
    175                     p[0] - p0_coords[0],
    176                     p[1] - p0_coords[1],
    177                     p[2] - p0_coords[2]
    178                 ],
    179                 t = Mat.innerProduct(diff, dir) / Mat.innerProduct(dir, dir),
    180                 t_clamped = Math.min(Math.max(t, this.range[0]), this.range[1]),
    181                 c3d;
    182
    183             c3d = this.getPointCoords(t_clamped).slice();
    184             c3d.unshift(1);
    185             return c3d;
    186         },
    187
    188         projectScreenCoords: function (pScr) {
    189             var end0 = this.getPointCoords(0),
    190                 end1 = this.getPointCoords(1);
    191
    192             return this.view.projectScreenToSegment(pScr, end0, end1);
    193         }
    194     }
    195 );
    196
    197 /**
    198  * @class This element is used to provide a constructor for a 3D line.
    199  * @pseudo
    200  * @description There are two possibilities to create a Line3D object.
    201  * <p>
    202  * First: the line in 3D is defined by two points in 3D (Point3D).
    203  * The points can be either existing points or coordinate arrays of
    204  * the form [x, y, z].
    205  * <p>Second: the line in 3D is defined by a point (or coordinate array [x, y, z])
    206  * a direction given as array [x, y, z] and an optional range
    207  * given as array [s, e]. The default value for the range is [-Infinity, Infinity].
    208  * <p>
    209  * All numbers can also be provided as functions returning a number.
    210  *
    211  * @name Line3D
    212  * @augments JXG.GeometryElement3D
    213  * @constructor
    214  * @type JXG.Line3D
    215  * @throws {Exception} If the element cannot be constructed with the given parent
    216  * objects an exception is thrown.
    217  * @param {JXG.Point3D,array,function_JXG.Point3D,array,function} point1,point2 First and second defining point.
    218  * @param {JXG.Point3D,array,function_array,function_array,function} point,direction,range Alternatively, point, direction and range can be supplied.
    219  * <ul>
    220  * <li> point: Point3D or array of length 3
    221  * <li> direction: array of length 3 or function returning an array of numbers or function returning an array
    222  * <li> range: array of length 2, elements can also be functions.
    223  * </ul>
    224  *
    225  * @example
    226  *     var bound = [-5, 5];
    227  *     var view = board.create('view3d',
    228  *         [[-6, -3], [8, 8],
    229  *         [bound, bound, bound]],
    230  *         {});
    231  *     var p = view.create('point3d', [1, 2, 2], { name:'A', size: 5 });
    232  *     // Lines through 2 points
    233  *     var l1 = view.create('line3d', [[1, 3, 3], [-3, -3, -3]], {point1: {visible: true}, point2: {visible: true} });
    234  *     var l2 = view.create('line3d', [p, l1.point1]);
    235  *
    236  *     // Line by point, direction, range
    237  *     var l3 = view.create('line3d', [p, [0, 0, 1], [-2, 4]]);
    238  *
    239  * </pre><div id='JXG05f9baa4-6059-4502-8911-6a934f823b3d' class='jxgbox' style='width: 300px; height: 300px;'></div>
    240  * <script type='text/javascript'>
    241  *     (function() {
    242  *         var board = JXG.JSXGraph.initBoard('JXG05f9baa4-6059-4502-8911-6a934f823b3d',
    243  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
    244  *         var bound = [-5, 5];
    245  *         var view = board.create('view3d',
    246  *             [[-6, -3], [8, 8],
    247  *             [bound, bound, bound]],
    248  *             {});
    249  *         var p = view.create('point3d', [1, 2, 2], { name:'A', size: 5 });
    250  *         // Lines through 2 points
    251  *         var l1 = view.create('line3d', [[1, 3, 3], [-3, -3, -3]], {name: 'll1', point1: {visible: true}, point2: {visible: true} });
    252  *         var l2 = view.create('line3d', [p, l1.point1]);
    253  *         // Line by point, direction, range
    254  *         var l3 = view.create('line3d', [p, [0, 0, 1], [-2, 4]]);
    255  *     })();
    256  *
    257  * </script><pre>
    258  *
    259  */
    260 JXG.createLine3D = function (board, parents, attributes) {
    261     var view = parents[0],
    262         attr, points,
    263         point, direction, range,
    264         point1, point2, endpoints,
    265         el;
    266
    267     attr = Type.copyAttributes(attributes, board.options, 'line3d');
    268
    269     // In any case, parents[1] contains a point or point coordinates
    270
    271     if (
    272         Type.isPoint3D(parents[2]) ||
    273         (parents.length === 3 && (Type.isArray(parents[2]) || Type.isFunction(parents[2])))
    274     ) {
    275         // Line defined by two points; [view, point1, point2]
    276
    277         point1 = Type.providePoints3D(view, [parents[1]], attributes, 'line3d', ['point1'])[0];
    278         point2 = Type.providePoints3D(view, [parents[2]], attributes, 'line3d', ['point2'])[0];
    279         direction = function () {
    280             return [point2.X() - point1.X(), point2.Y() - point1.Y(), point2.Z() - point1.Z()];
    281         };
    282         range = [0, 1]; // Segment by default
    283         el = new JXG.Line3D(view, point1, direction, range, attr);
    284
    285         // Create two shadow points that are the end points of the visible line.
    286         // This is of relevance if the line has straightFirst or straightLast set to true, then
    287         // endpoints differ from point1, point2.
    288         // In such a case, the endpoints are the intersection of the line with the cube.
    289         endpoints = Type.providePoints3D(
    290             view,
    291             [
    292                 [0, 0, 0],
    293                 [0, 0, 0]
    294             ],
    295             { visible: false },
    296             'line3d',
    297             ['point1', 'point2']
    298         );
    299
    300         endpoints[0].F = function () {
    301             var r = 0;
    302             if (Type.evaluate(el.visProp.straightfirst)) {
    303                 r = -Infinity;
    304             }
    305             return el.getPointCoords(r);
    306         };
    307         endpoints[1].F = function () {
    308             var r = 1;
    309             if (Type.evaluate(el.visProp.straightlast)) {
    310                 r = Infinity;
    311             }
    312             return el.getPointCoords(r);
    313         };
    314         endpoints[0].prepareUpdate().update();
    315         endpoints[1].prepareUpdate().update();
    316
    317         // The 2D line is always a segment.
    318         attr = el.setAttr2D(attr);
    319         attr.straightfirst = false;
    320         attr.straightlast = false;
    321         el.element2D = view.create('segment', [endpoints[0].element2D, endpoints[1].element2D], attr);
    322         el.element2D.view = view;
    323
    324         /**
    325          * Shadow points that determine the visible line.
    326          * This is of relevance if the line is defined by two points and has straightFirst or straightLast set to true.
    327          * In such a case, the shadow points are the intersection of the line with the cube.
    328          *
    329          * @name JXG.Point3D.endpoints
    330          * @type Array
    331          * @private
    332          */
    333         el.endpoints = endpoints;
    334         el.addChild(endpoints[0]);
    335         el.addChild(endpoints[1]);
    336         // el.setParents(endpoints);
    337         el.addParents([point1, point2]);
    338
    339     } else {
    340         // Line defined by point, direction and range
    341
    342         point = Type.providePoints3D(view, [parents[1]], attributes, 'line3d', ['point'])[0];
    343
    344         // Directions are handled as arrays of length 4,
    345         // i.e. with homogeneous coordinates.
    346         if (Type.isFunction(parents[2])) {
    347             direction = parents[2];
    348         } else if (parents[2].length === 3) {
    349             direction = [1].concat(parents[2]);
    350         } else if (parents[2].length === 4) {
    351             direction = parents[2];
    352         } else {
    353             // TODO Throw error
    354         }
    355         range = parents[3];
    356
    357         points = Type.providePoints3D(
    358             view,
    359             [
    360                 [0, 0, 0],
    361                 [0, 0, 0]
    362             ],
    363             attributes,
    364             'line3d',
    365             ['point1', 'point2']
    366         );
    367
    368         // Create a line3d with two dummy points
    369         el = new JXG.Line3D(view, point, direction, range, attr);
    370
    371         // Now set the real points which define the line
    372         /**
    373          * @class
    374          * @ignore
    375          */
    376         points[0].F = function () {
    377             return el.getPointCoords(Type.evaluate(el.range[0]));
    378         };
    379         points[0].prepareUpdate().update();
    380         point1 = points[0];
    381
    382         /**
    383          * @class
    384          * @ignore
    385          */
    386         points[1].F = function () {
    387             return el.getPointCoords(Type.evaluate(el.range[1]));
    388         };
    389         points[1].prepareUpdate().update();
    390         point2 = points[1];
    391
    392         attr = el.setAttr2D(attr);
    393         attr.straightfirst = false;
    394         attr.straightlast = false;
    395         el.element2D = view.create('segment', [point1.element2D, point2.element2D], attr);
    396         el.element2D.view = view;
    397
    398         el.endpoints = points;
    399         el.addParents(point);
    400     }
    401
    402     // TODO Throw error
    403
    404     el.addChild(el.element2D);
    405     el.inherits.push(el.element2D);
    406     el.element2D.addParents(el);
    407
    408     el.point1 = point1;
    409     el.point2 = point2;
    410     if (el.point1._is_new) {
    411         el.addChild(el.point1);
    412         delete el.point1._is_new;
    413     } else {
    414         el.point1.addChild(el);
    415     }
    416     if (el.point2._is_new) {
    417         el.addChild(el.point2);
    418         delete el.point2._is_new;
    419     } else {
    420         el.point2.addChild(el);
    421     }
    422     if (Type.exists(point)) {
    423         if (point._is_new) {
    424             el.addChild(point);
    425             delete point._is_new;
    426         } else {
    427             point.addChild(el);
    428         }
    429     }
    430
    431     el.update();
    432     el.element2D.prepareUpdate().update().updateRenderer();
    433     return el;
    434 };
    435 JXG.registerElement('line3d', JXG.createLine3D);
    436
    437 // -----------------------
    438 //  Planes
    439 // -----------------------
    440
    441 /**
    442  * Constructor for 3D planes.
    443  * @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.
    444  *
    445  * @augments JXG.GeometryElement3D
    446  * @augments JXG.GeometryElement
    447  * @param {View3D} view
    448  * @param {Point3D|Array} point
    449  * @param {Array} direction1
    450  * @param {Array} range1
    451  * @param {Array} direction2
    452  * @param {Array} range2
    453  * @param {Object} attributes
    454  * @see JXG.Board#generateName
    455  */
    456 JXG.Plane3D = function (view, point, dir1, range1, dir2, range2, attributes) {
    457     this.constructor(view.board, attributes, Const.OBJECT_TYPE_PLANE3D, Const.OBJECT_CLASS_3D);
    458     this.constructor3D(view, 'plane3d');
    459
    460     this.board.finalizeAdding(this);
    461
    462     /**
    463      * 3D point which - together with two direction vectors - defines the plane.
    464      *
    465      * @type JXG.Point3D
    466      *
    467      * @see JXG.3D#direction1
    468      * @see JXG.3D#direction2
    469      */
    470     this.point = point;
    471
    472     /**
    473      * Two linearly independent vectors - together with a point - define the plane. Each of these direction vectors is an
    474      * array of numbers or functions (of length 3) or function returning array of length 3.
    475      *
    476      * @type Array|Function
    477      *
    478      * @see JXG.Plane3D#point
    479      * @see JXG.Plane3D#direction2
    480      */
    481     this.direction1 = dir1;
    482
    483     /**
    484      * Two linearly independent vectors - together with a point - define the plane. Each of these direction vectors is an
    485      * array of numbers or functions (of length 3) or function returning array of length 3.
    486      *
    487      * @type Array|Function
    488      * @see JXG.Plane3D#point
    489      * @see JXG.Plane3D#direction1
    490      */
    491     this.direction2 = dir2;
    492
    493     /**
    494      * Range [r1, r2] of {@link direction1}. The 3D line goes from (point + r1 * direction1) to (point + r2 * direction1)
    495      * @type {Array}
    496      */
    497     this.range1 = range1 || [-Infinity, Infinity];
    498
    499     /**
    500      * Range [r1, r2] of {@link direction2}. The 3D line goes from (point + r1 * direction2) to (point + r2 * direction2)
    501      * @type {Array}
    502      */
    503     this.range2 = range2 || [-Infinity, Infinity];
    504
    505     /**
    506      * Direction vector 1 of the 3D plane. Contains the evaluated coordinates from {@link direction1} and {@link range1}.
    507      * @type Array
    508      * @private
    509      *
    510      * @see updateNormal
    511      */
    512     this.vec1 = [0, 0, 0];
    513
    514     /**
    515      * Direction vector 2 of the 3D plane. Contains the evaluated coordinates from {@link direction2} and {@link range2}.
    516      *
    517      * @type Array
    518      * @private
    519      *
    520      * @see updateNormal
    521      */
    522     this.vec2 = [0, 0, 0];
    523
    524     this.grid = null;
    525
    526     /**
    527          * Normal vector of the plane. Left hand side of the Hesse normal form.
    528
    529         * @type Array
    530          * @private
    531          *
    532          * @see updateNormal
    533          *
    534          */
    535     this.normal = [0, 0, 0];
    536
    537     /**
    538          * Right hand side of the Hesse normal form.
    539
    540         * @type Array
    541          * @private
    542          *
    543          * @see updateNormal
    544          *
    545          */
    546     this.d = 0;
    547
    548     this.updateNormal();
    549
    550     this.methodMap = Type.deepCopy(this.methodMap, {
    551         // TODO
    552     });
    553 };
    554 JXG.Plane3D.prototype = new JXG.GeometryElement();
    555 Type.copyPrototypeMethods(JXG.Plane3D, JXG.GeometryElement3D, 'constructor3D');
    556
    557 JXG.extend(
    558     JXG.Plane3D.prototype,
    559     /** @lends JXG.Plane3D.prototype */ {
    560         /**
    561          * Update the Hesse normal form of the plane, i.e. update normal vector and right hand side.
    562          * Updates also {@link vec1} and {@link vec2}.
    563          *
    564          * @name JXG.Plane3D#updateNormal
    565          * @function
    566          * @returns {Object} Reference to the Plane3D object
    567          * @private
    568          * @example
    569          *    plane.updateNormal();
    570          *
    571          */
    572         updateNormal: function () {
    573             var i, len;
    574
    575             if (Type.isFunction(this.direction1)) {
    576                 this.vec1 = Type.evaluate(this.direction1);
    577             } else {
    578                 for (i = 0; i < 3; i++) {
    579                     this.vec1[i] = Type.evaluate(this.direction1[i]);
    580                 }
    581             }
    582             if (Type.isFunction(this.direction2)) {
    583                 this.vec2 = Type.evaluate(this.direction2);
    584             } else {
    585                 for (i = 0; i < 3; i++) {
    586                     this.vec2[i] = Type.evaluate(this.direction2[i]);
    587                 }
    588             }
    589
    590             this.normal = Mat.crossProduct(this.vec1, this.vec2);
    591
    592             len = Mat.norm(this.normal);
    593             if (Math.abs(len) > Mat.eps) {
    594                 for (i = 0; i < 3; i++) {
    595                     this.normal[i] /= len;
    596                 }
    597             }
    598             this.d = Mat.innerProduct(this.point.coords.slice(1), this.normal, 3);
    599
    600             return this;
    601         },
    602
    603         updateDataArray: function () {
    604             var s1, e1, s2, e2, c2d, l1, l2,
    605                 planes = ['xPlaneRear', 'yPlaneRear', 'zPlaneRear'],
    606                 points = [],
    607                 v1 = [0, 0, 0],
    608                 v2 = [0, 0, 0],
    609                 q = [0, 0, 0],
    610                 p = [0, 0, 0],
    611                 d, i, j, a, b, first, pos, pos_akt,
    612                 view = this.view;
    613
    614             this.dataX = [];
    615             this.dataY = [];
    616
    617             this.updateNormal();
    618
    619             // Infinite plane
    620             if (
    621                 this.elType !== 'axisplane3d' &&
    622                 view.defaultAxes &&
    623                 Type.evaluate(this.range1[0]) === -Infinity &&
    624                 Type.evaluate(this.range1[1]) === Infinity &&
    625                 Type.evaluate(this.range2[0]) === -Infinity &&
    626                 Type.evaluate(this.range2[1]) === Infinity
    627             ) {
    628                 // Start with the rear plane.
    629                 // Determine the intersections with the view bbox3d
    630                 // For each face of the bbox3d we determine two points
    631                 // which are the ends of the intersection line.
    632                 // We start with the three rear planes.
    633                 for (j = 0; j < planes.length; j++) {
    634                     p = view.intersectionPlanePlane(this, view.defaultAxes[planes[j]]);
    635
    636                     if (p[0].length === 3 && p[1].length === 3) {
    637                         // This test is necessary to filter out intersection lines which are
    638                         // identical to intersections of axis planes (they would occur twice).
    639                         for (i = 0; i < points.length; i++) {
    640                             if (
    641                                 (Geometry.distance(p[0], points[i][0], 3) < Mat.eps &&
    642                                     Geometry.distance(p[1], points[i][1], 3) < Mat.eps) ||
    643                                 (Geometry.distance(p[0], points[i][1], 3) < Mat.eps &&
    644                                     Geometry.distance(p[1], points[i][0], 3) < Mat.eps)
    645                             ) {
    646                                 break;
    647                             }
    648                         }
    649                         if (i === points.length) {
    650                             points.push(p.slice());
    651                         }
    652                     }
    653
    654                     // Point on the front plane of the bbox3d
    655                     p = [0, 0, 0];
    656                     p[j] = view.bbox3D[j][1];
    657
    658                     // d is the rhs of the Hesse normal form of the front plane.
    659                     d = Mat.innerProduct(p, view.defaultAxes[planes[j]].normal, 3);
    660                     p = view.intersectionPlanePlane(this, view.defaultAxes[planes[j]], d);
    661
    662                     if (p[0].length === 3 && p[1].length === 3) {
    663                         // Do the same test as above
    664                         for (i = 0; i < points.length; i++) {
    665                             if (
    666                                 (Geometry.distance(p[0], points[i][0], 3) < Mat.eps &&
    667                                     Geometry.distance(p[1], points[i][1], 3) < Mat.eps) ||
    668                                 (Geometry.distance(p[0], points[i][1], 3) < Mat.eps &&
    669                                     Geometry.distance(p[1], points[i][0], 3) < Mat.eps)
    670                             ) {
    671                                 break;
    672                             }
    673                         }
    674                         if (i === points.length) {
    675                             points.push(p.slice());
    676                         }
    677                     }
    678                 }
    679
    680                 // Concatenate the intersection points to a polygon.
    681                 // If all went well, each intersection should appear
    682                 // twice in the list.
    683                 first = 0;
    684                 pos = first;
    685                 i = 0;
    686                 do {
    687                     p = points[pos][i];
    688                     if (p.length === 3) {
    689                         c2d = view.project3DTo2D(p);
    690                         this.dataX.push(c2d[1]);
    691                         this.dataY.push(c2d[2]);
    692                     }
    693                     i = (i + 1) % 2;
    694                     p = points[pos][i];
    695
    696                     pos_akt = pos;
    697                     for (j = 0; j < points.length; j++) {
    698                         if (j !== pos && Geometry.distance(p, points[j][0]) < Mat.eps) {
    699                             pos = j;
    700                             i = 0;
    701                             break;
    702                         }
    703                         if (j !== pos && Geometry.distance(p, points[j][1]) < Mat.eps) {
    704                             pos = j;
    705                             i = 1;
    706                             break;
    707                         }
    708                     }
    709                     if (pos === pos_akt) {
    710                         console.log('Error: update plane3d: did not find next', pos);
    711                         break;
    712                     }
    713                 } while (pos !== first);
    714
    715                 c2d = view.project3DTo2D(points[first][0]);
    716                 this.dataX.push(c2d[1]);
    717                 this.dataY.push(c2d[2]);
    718             } else {
    719                 // 3D bounded flat
    720                 s1 = Type.evaluate(this.range1[0]);
    721                 e1 = Type.evaluate(this.range1[1]);
    722                 s2 = Type.evaluate(this.range2[0]);
    723                 e2 = Type.evaluate(this.range2[1]);
    724
    725                 q = this.point.coords.slice(1);
    726
    727                 v1 = this.vec1.slice();
    728                 v2 = this.vec2.slice();
    729                 l1 = Mat.norm(v1, 3);
    730                 l2 = Mat.norm(v2, 3);
    731                 for (i = 0; i < 3; i++) {
    732                     v1[i] /= l1;
    733                     v2[i] /= l2;
    734                 }
    735
    736                 for (j = 0; j < 4; j++) {
    737                     switch (j) {
    738                         case 0:
    739                             a = s1;
    740                             b = s2;
    741                             break;
    742                         case 1:
    743                             a = e1;
    744                             b = s2;
    745                             break;
    746                         case 2:
    747                             a = e1;
    748                             b = e2;
    749                             break;
    750                         case 3:
    751                             a = s1;
    752                             b = e2;
    753                     }
    754                     for (i = 0; i < 3; i++) {
    755                         p[i] = q[i] + a * v1[i] + b * v2[i];
    756                     }
    757                     c2d = view.project3DTo2D(p);
    758                     this.dataX.push(c2d[1]);
    759                     this.dataY.push(c2d[2]);
    760                 }
    761                 // Close the curve
    762                 this.dataX.push(this.dataX[0]);
    763                 this.dataY.push(this.dataY[0]);
    764             }
    765             return { X: this.dataX, Y: this.dataY };
    766         },
    767
    768         update: function () {
    769             return this;
    770         },
    771
    772         updateRenderer: function () {
    773             this.needsUpdate = false;
    774             return this;
    775         }
    776     }
    777 );
    778
    779 // TODO docs
    780 JXG.createPlane3D = function (board, parents, attributes) {
    781     var view = parents[0],
    782         attr,
    783         point,
    784         dir1 = parents[2],
    785         dir2 = parents[3],
    786         range1 = parents[4] || [-Infinity, Infinity],
    787         range2 = parents[5] || [-Infinity, Infinity],
    788         el,
    789         grid;
    790
    791     point = Type.providePoints3D(view, [parents[1]], attributes, 'plane3d', ['point'])[0];
    792     if (point === false) {
    793         // TODO Throw error
    794     }
    795
    796     attr = Type.copyAttributes(attributes, board.options, 'plane3d');
    797     el = new JXG.Plane3D(view, point, dir1, range1, dir2, range2, attr);
    798     point.addChild(el);
    799
    800     attr = el.setAttr2D(attr);
    801     el.element2D = view.create('curve', [[], []], attr);
    802     el.element2D.view = view;
    803
    804     /**
    805      * @class
    806      * @ignore
    807      */
    808     el.element2D.updateDataArray = function () {
    809         var ret = el.updateDataArray();
    810         this.dataX = ret.X;
    811         this.dataY = ret.Y;
    812     };
    813     el.addChild(el.element2D);
    814     el.inherits.push(el.element2D);
    815     el.element2D.setParents(el);
    816
    817     attr = Type.copyAttributes(attributes.mesh3d, board.options, 'mesh3d');
    818     if (
    819         Math.abs(el.range1[0]) !== Infinity &&
    820         Math.abs(el.range1[1]) !== Infinity &&
    821         Math.abs(el.range2[0]) !== Infinity &&
    822         Math.abs(el.range2[1]) !== Infinity
    823     ) {
    824         grid = view.create('mesh3d', [
    825             function () {
    826                 return point.coords;
    827             },
    828             dir1, range1, dir2, range2
    829         ], attr
    830         );
    831         el.grid = grid;
    832         el.addChild(grid);
    833         el.inherits.push(grid);
    834         grid.setParents(el);
    835         el.grid.view = view;
    836
    837     }
    838
    839     el.element2D.prepareUpdate().update();
    840     if (!board.isSuspendedUpdate) {
    841         el.element2D.updateVisibility().updateRenderer();
    842     }
    843
    844     return el;
    845 };
    846
    847 JXG.registerElement('plane3d', JXG.createPlane3D);
    848
    849 /**
    850  * @class An intersection line is a line which lives on two JSXGraph elements.
    851  * The following element types can be (mutually) intersected: plane.
    852  *
    853  * @pseudo
    854  * @name IntersectionLine3D
    855  * @augments JXG.Line3D
    856  * @constructor
    857  * @type JXG.Line3D
    858  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    859  * @param {JXG.Plane3D_JXG.Plane3D} el1,el2 The result will be the intersection of el1 and el2.
    860  * @example
    861  * // Create the intersection line of two planes
    862  * var view = board.create(
    863  *     'view3d',
    864  *     [[-6, -3], [8, 8],
    865  *     [[0, 3], [0, 3], [0, 3]]],
    866  *     {
    867  *         xPlaneRear: {fillOpacity: 0.2, gradient: null},
    868  *         yPlaneRear: {fillOpacity: 0.2, gradient: null},
    869  *         zPlaneRear: {fillOpacity: 0.2, gradient: null}
    870  *     }
    871  * );
    872  * var a = view.create('point3d', [0, 0, 0]);
    873  *
    874  * var p1 = view.create(
    875  *    'plane3d',
    876  *     [a, [1, 0, 0], [0, 1, 0]],
    877  *     {fillColor: '#00ff80'}
    878  * );
    879  * var p2 = view.create(
    880  *    'plane3d',
    881  *     [a, [-2, 1, 1], [1, -2, 1]],
    882  *     {fillColor: '#ff0000'}
    883  * );
    884  *
    885  * var i = view.create('intersectionline3d', [p1, p2]);
    886  *
    887  * </pre><div id="JXGdb931076-b29a-4eff-b97e-4251aaf24943" class="jxgbox" style="width: 300px; height: 300px;"></div>
    888  * <script type="text/javascript">
    889  *     (function() {
    890  *         var board = JXG.JSXGraph.initBoard('JXGdb931076-b29a-4eff-b97e-4251aaf24943',
    891  *             {boundingbox: [-8, 8, 8,-8], axis: false, pan: {enabled: false}, showcopyright: false, shownavigation: false});
    892  *         var view = board.create(
    893  *             'view3d',
    894  *             [[-6, -3], [8, 8],
    895  *             [[0, 3], [0, 3], [0, 3]]],
    896  *             {
    897  *                 xPlaneRear: {fillOpacity: 0.2, gradient: null},
    898  *                 yPlaneRear: {fillOpacity: 0.2, gradient: null},
    899  *                 zPlaneRear: {fillOpacity: 0.2, gradient: null}
    900  *             }
    901  *         );
    902  *     var a = view.create('point3d', [0, 0, 0]);
    903  *
    904  *     var p1 = view.create(
    905  *        'plane3d',
    906  *         [a, [1, 0, 0], [0, 1, 0]],
    907  *         {fillColor: '#00ff80'}
    908  *     );
    909  *     var p2 = view.create(
    910  *        'plane3d',
    911  *         [a, [-2, 1, 1], [1, -2, 1]],
    912  *         {fillColor: '#ff0000'}
    913  *     );
    914  *
    915  *     var i = view.create('intersectionline3d', [p1, p2]);
    916  *
    917  *     })();
    918  *
    919  * </script><pre>
    920  *
    921  */
    922 JXG.createIntersectionLine3D = function (board, parents, attributes) {
    923     var view = parents[0],
    924         el1 = parents[1],
    925         el2 = parents[2],
    926         ixnLine, i, func,
    927         attr = Type.copyAttributes(attributes, board.options, "intersectionline3d"),
    928         pts = [];
    929
    930     for (i = 0; i < 2; i++) {
    931         func = Geometry.intersectionFunction3D(view, el1, el2, i);
    932         pts[i] = view.create('point3d', func, attr['point' + (i + 1)]);
    933     }
    934     ixnLine = view.create('line3d', pts, attr);
    935
    936     try {
    937         el1.addChild(ixnLine);
    938         el2.addChild(ixnLine);
    939     } catch (_e) {
    940         throw new Error(
    941             "JSXGraph: Can't create 'intersection' with parent types '" +
    942             typeof parents[0] +
    943             "' and '" +
    944             typeof parents[1] +
    945             "'."
    946         );
    947     }
    948
    949     ixnLine.type = Const.OBJECT_TYPE_INTERSECTION_LINE3D;
    950     ixnLine.elType = 'intersectionline3d';
    951     ixnLine.setParents([el1.id, el2.id]);
    952
    953     return ixnLine;
    954 };
    955
    956 JXG.registerElement('intersectionline3d', JXG.createIntersectionLine3D);
    957