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