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