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