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