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