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