1 /*
  2     Copyright 2008-2023
  3         Matthias Ehmann,
  4         Carsten Miller,
  5         Andreas Walter,
  6         Alfred Wassermann
  7 
  8     This file is part of JSXGraph.
  9 
 10     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 11 
 12     You can redistribute it and/or modify it under the terms of the
 13 
 14       * GNU Lesser General Public License as published by
 15         the Free Software Foundation, either version 3 of the License, or
 16         (at your option) any later version
 17       OR
 18       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 19 
 20     JSXGraph is distributed in the hope that it will be useful,
 21     but WITHOUT ANY WARRANTY; without even the implied warranty of
 22     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 23     GNU Lesser General Public License for more details.
 24 
 25     You should have received a copy of the GNU Lesser General Public License and
 26     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
 27     and <https://opensource.org/licenses/MIT/>.
 28  */
 29 /*global JXG:true, define: true*/
 30 
 31 import JXG from "../jxg";
 32 import Const from "../base/constants";
 33 import Type from "../utils/type";
 34 import Mat from "../math/math";
 35 import GeometryElement from "../base/element";
 36 import Composition from "../base/composition";
 37 
 38 /**
 39  * 3D view inside of a JXGraph board.
 40  *
 41  * @class Creates a new 3D view. Do not use this constructor to create a 3D view. Use {@link JXG.Board#create} with
 42  * type {@link View3D} instead.
 43  *
 44  * @augments JXG.GeometryElement
 45  * @param {Array} parents Array consisting of lower left corner [x, y] of the view inside the board, [width, height] of the view
 46  * and box size [[x1, x2], [y1,y2], [z1,z2]]. If the view's azimuth=0 and elevation=0, the 3D view will cover a rectangle with lower left corner
 47  * [x,y] and side lengths [w, h] of the board.
 48  */
 49 JXG.View3D = function (board, parents, attributes) {
 50     this.constructor(board, attributes, Const.OBJECT_TYPE_VIEW3D, Const.OBJECT_CLASS_3D);
 51 
 52     /**
 53      * An associative array containing all geometric objects belonging to the view.
 54      * Key is the id of the object and value is a reference to the object.
 55      * @type Object
 56      * @private
 57      */
 58     this.objects = {};
 59 
 60     /**
 61      * An array containing all geometric objects in this view in the order of construction.
 62      * @type Array
 63      * @private
 64      */
 65     // this.objectsList = [];
 66 
 67     /**
 68      * An associative array / dictionary to store the objects of the board by name. The name of the object is the key and value is a reference to the object.
 69      * @type Object
 70      * @private
 71      */
 72     this.elementsByName = {};
 73 
 74     /**
 75      * Default axes of the 3D view, contains the axes of the view or null.
 76      *
 77      * @type {Object}
 78      * @default null
 79      */
 80     this.defaultAxes = null;
 81 
 82     /**
 83      * @type  {Array}
 84      * @private
 85     */
 86     // 3 x 4 matrix
 87     // 3D-to-2D transformation matrix
 88     this.matrix3D = [
 89         [1, 0, 0, 0],
 90         [0, 1, 0, 0],
 91         [0, 0, 1, 0]
 92     ];
 93 
 94     /**
 95      * @type array
 96      * @private
 97     */
 98     //     Lower left corner [x, y] of the 3D view if elevation and azimuth are set to 0.
 99     this.llftCorner = parents[0];
100 
101     /**
102      * Width and height [w, h] of the 3D view if elevation and azimuth are set to 0.
103      * @type array
104      * @private
105      */
106     this.size = parents[1];
107 
108     /**
109      * Bounding box (cube) [[x1, x2], [y1,y2], [z1,z2]] of the 3D view
110      * @type array
111      */
112     this.bbox3D = parents[2];
113 
114     /**
115      * Distance of the view to the origin. In other words, its
116      * the radius of the sphere where the camera sits.view.board.update
117      * @type Number
118      */
119     this.r = -1;
120 
121     this.projectionType = 'parallel';
122 
123     this.timeoutAzimuth = null;
124 
125     this.id = this.board.setId(this, "V");
126     this.board.finalizeAdding(this);
127     this.elType = "view3d";
128 
129     this.methodMap = Type.deepCopy(this.methodMap, {
130         // TODO
131     });
132 };
133 JXG.View3D.prototype = new GeometryElement();
134 
135 JXG.extend(
136     JXG.View3D.prototype, /** @lends JXG.View3D.prototype */ {
137 
138     /**
139      * Creates a new 3D element of type elementType.
140      * @param {String} elementType Type of the element to be constructed given as a string e.g. 'point3d' or 'surface3d'.
141      * @param {Array} parents Array of parent elements needed to construct the element e.g. coordinates for a 3D point or two
142      * 3D points to construct a line. This highly depends on the elementType that is constructed. See the corresponding JXG.create*
143      * methods for a list of possible parameters.
144      * @param {Object} [attributes] An object containing the attributes to be set. This also depends on the elementType.
145      * Common attributes are name, visible, strokeColor.
146      * @returns {Object} Reference to the created element. This is usually a GeometryElement3D, but can be an array containing
147      * two or more elements.
148      */
149     create: function (elementType, parents, attributes) {
150         var prefix = [],
151             // is3D = false,
152             el;
153 
154         if (elementType.indexOf("3d") > 0) {
155             // is3D = true;
156             prefix.push(this);
157         }
158         el = this.board.create(elementType, prefix.concat(parents), attributes);
159 
160         return el;
161     },
162 
163     /**
164      * Select a single or multiple elements at once.
165      * @param {String|Object|function} str The name, id or a reference to a JSXGraph 3D element in the 3D view. An object will
166      * be used as a filter to return multiple elements at once filtered by the properties of the object.
167      * @param {Boolean} onlyByIdOrName If true (default:false) elements are only filtered by their id, name or groupId.
168      * The advanced filters consisting of objects or functions are ignored.
169      * @returns {JXG.GeometryElement3D|JXG.Composition}
170      * @example
171      * // select the element with name A
172      * view.select('A');
173      *
174      * // select all elements with strokecolor set to 'red' (but not '#ff0000')
175      * view.select({
176      *   strokeColor: 'red'
177      * });
178      *
179      * // select all points on or below the x/y plane and make them black.
180      * view.select({
181      *   elType: 'point3d',
182      *   Z: function (v) {
183      *     return v <= 0;
184      *   }
185      * }).setAttribute({color: 'black'});
186      *
187      * // select all elements
188      * view.select(function (el) {
189      *   return true;
190      * });
191      */
192     select: function (str, onlyByIdOrName) {
193         var flist,
194             olist,
195             i,
196             l,
197             s = str;
198 
199         if (s === null) {
200             return s;
201         }
202 
203         // It's a string, most likely an id or a name.
204         if (Type.isString(s) && s !== "") {
205             // Search by ID
206             if (Type.exists(this.objects[s])) {
207                 s = this.objects[s];
208                 // Search by name
209             } else if (Type.exists(this.elementsByName[s])) {
210                 s = this.elementsByName[s];
211                 // // Search by group ID
212                 // } else if (Type.exists(this.groups[s])) {
213                 //     s = this.groups[s];
214             }
215 
216             // It's a function or an object, but not an element
217         } else if (
218             !onlyByIdOrName &&
219             (Type.isFunction(s) || (Type.isObject(s) && !Type.isFunction(s.setAttribute)))
220         ) {
221             flist = Type.filterElements(this.objectsList, s);
222 
223             olist = {};
224             l = flist.length;
225             for (i = 0; i < l; i++) {
226                 olist[flist[i].id] = flist[i];
227             }
228             s = new Composition(olist);
229 
230             // It's an element which has been deleted (and still hangs around, e.g. in an attractor list
231         } else if (
232             Type.isObject(s) &&
233             Type.exists(s.id) &&
234             !Type.exists(this.objects[s.id])
235         ) {
236             s = null;
237         }
238 
239         return s;
240     },
241 
242     updateParallelProjection: function () {
243         var r, a, e, f,
244             mat = [
245                 [1, 0, 0, 0],
246                 [0, 1, 0, 0],
247                 [0, 0, 1, 0]
248             ];
249 
250         // mat projects homogeneous 3D coords in View3D
251         // to homogeneous 2D coordinates in the board
252         e = this.el_slide.Value();
253         r = this.r;
254         a = this.az_slide.Value();
255         f = r * Math.sin(e);
256 
257         mat[1][1] = r * Math.cos(a);
258         mat[1][2] = -r * Math.sin(a);
259         mat[2][1] = f * Math.sin(a);
260         mat[2][2] = f * Math.cos(a);
261         mat[2][3] = Math.cos(e);
262 
263         return mat;
264     },
265 
266     updateCentralProjection: function () {
267         var r, e, a,
268             az, ax, ay, v, nrm,
269             // See https://www.mathematik.uni-marburg.de/~thormae/lectures/graphics1/graphics_6_1_eng_web.html
270             // bbox3D is always at the world origin, i.e. T_obj is the unit matrix.
271             // All vectors contain affine coordinates and have length 3
272             // The matrices are of size 4x4.
273             Tcam1, // The inverse camera transformation
274             eye, d,
275             foc = 1 / Math.tan(0.5 * Math.PI / 2),
276             zf = 20,
277             zn = 8,
278             Pref = [
279                 0.5 * (this.bbox3D[0][0] + this.bbox3D[0][1]),
280                 0.5 * (this.bbox3D[0][0] + this.bbox3D[0][1]),
281                 0.5 * (this.bbox3D[0][0] + this.bbox3D[0][1])
282             ],
283             up = [0, 0, 1],
284             A = [
285                 [0, 0, 0, -1],
286                 [0, foc, 0, 0],
287                 [0, 0, foc, 0],
288                 [2 * zf * zn / (zn - zf), 0, 0, (zf + zn) / (zn - zf)]
289             ];
290 
291         a = this.az_slide.Value();
292         e = this.el_slide.Value() * 2;
293         // r = this.r;
294 
295         // Sphere
296         r = 12;
297         a += 3 * Math.PI * 0.5;
298         eye = [
299             r * Math.cos(a) * Math.cos(e),
300             -r * Math.sin(a) * Math.cos(e),
301             r * Math.sin(e)
302         ];
303 
304         // Circle
305         // r = 8;
306         // // up = [0, Math.cos(e), Math.sin(e)];
307         // up = [0, 0, 1];
308         // eye = [
309         //     -r * Math.cos(a),
310         //     -r * Math.sin(a),
311         //     1. * r
312         // ];
313 
314         d = [eye[0] - Pref[0], eye[1] - Pref[1], eye[2] - Pref[2]];
315         nrm = Mat.norm(d, 3);
316         az = [d[0] / nrm, d[1] / nrm, d[2] / nrm];
317 
318         nrm = Mat.norm(up, 3);
319         v = [up[0] / nrm, up[1] / nrm, up[2] / nrm];
320 
321         ax = Mat.crossProduct(v, az);
322         ay = Mat.crossProduct(az, ax);
323 
324         v = Mat.matVecMult([ax, ay, az], eye);
325         Tcam1 = [
326             [1, 0, 0, 0],
327             [-v[0], ax[0], ax[1], ax[2]],
328             [-v[1], ay[0], ay[1], ay[2]],
329             [-v[2], az[0], az[1], az[2]]
330         ];
331         A = Mat.matMatMult(A, Tcam1);
332 
333         return A;
334     },
335 
336     update: function () {
337         // Update 3D-to-2D transformation matrix with the actual
338         // elevation and azimuth angles.
339 
340         var mat2D, shift, size;
341 
342         if (
343             !Type.exists(this.el_slide) ||
344             !Type.exists(this.az_slide) ||
345             !this.needsUpdate
346         ) {
347             return this;
348         }
349 
350         mat2D = [
351             [1, 0, 0],
352             [0, 1, 0],
353             [0, 0, 1]
354         ];
355 
356         this.projectionType = Type.evaluate(this.visProp.projection);
357 
358         if (this.projectionType === 'parallel') {
359             // Parallel projection
360             // Rotate the scenery around the center of the box,
361             // not around the origin
362             shift = [
363                 [1, 0, 0, 0],
364                 [-0.5 * (this.bbox3D[0][0] + this.bbox3D[0][1]), 1, 0, 0],
365                 [-0.5 * (this.bbox3D[1][0] + this.bbox3D[1][1]), 0, 1, 0],
366                 [-0.5 * (this.bbox3D[2][0] + this.bbox3D[2][1]), 0, 0, 1]
367             ];
368 
369             // Add a second transformation to scale and shift the projection
370             // on the board, usually called viewport.
371             mat2D[1][1] = this.size[0] / (this.bbox3D[0][1] - this.bbox3D[0][0]); // w / d_x
372             mat2D[2][2] = this.size[1] / (this.bbox3D[1][1] - this.bbox3D[1][0]); // h / d_y
373             mat2D[1][0] = this.llftCorner[0] + mat2D[1][1] * 0.5 * (this.bbox3D[0][1] - this.bbox3D[0][0]); // llft_x
374             mat2D[2][0] = this.llftCorner[1] + mat2D[2][2] * 0.5 * (this.bbox3D[1][1] - this.bbox3D[1][0]); // llft_y
375 
376             // this.matrix3D is a 3x4 matrix
377             this.matrix3D = this.updateParallelProjection();
378             // Combine the projections
379             this.matrix3D = Mat.matMatMult(mat2D, Mat.matMatMult(this.matrix3D, shift));
380 
381         } else {
382             // Central projection
383             // this.matrix3D is a 4x4 matrix
384             this.matrix3D = this.updateCentralProjection();
385 
386             size = 0.4;
387             mat2D[1][1] = this.size[0] / (2 * size); // w / d_x
388             mat2D[2][2] = this.size[1] / (2 * size); // h / d_y
389             mat2D[1][0] = this.llftCorner[0] + mat2D[1][1] * 0.5 * (2 * size); // llft_x
390             mat2D[2][0] = this.llftCorner[1] + mat2D[2][2] * 0.5 * (2 * size); // llft_y
391 
392             // The transformations this.matrix3D and mat2D can not be combined yet, since
393             // the projected vector has to be normalized in between in
394             // project3DTo2D
395             this.viewPortTransform = mat2D;
396         }
397 
398         return this;
399     },
400 
401     updateRenderer: function () {
402         this.needsUpdate = false;
403         return this;
404     },
405 
406     removeObject: function (object, saveMethod) {
407         var i;
408 
409         // this.board.removeObject(object, saveMethod);
410         if (Type.isArray(object)) {
411             for (i = 0; i < object.length; i++) {
412                 this.removeObject(object[i]);
413             }
414             return this;
415         }
416 
417         object = this.select(object);
418 
419         // // If the object which is about to be removed unknown or a string, do nothing.
420         // // it is a string if a string was given and could not be resolved to an element.
421         if (!Type.exists(object) || Type.isString(object)) {
422             return this;
423         }
424 
425         try {
426             //     // remove all children.
427             //     for (el in object.childElements) {
428             //         if (object.childElements.hasOwnProperty(el)) {
429             //             object.childElements[el].board.removeObject(object.childElements[el]);
430             //         }
431             //     }
432 
433             delete this.objects[object.id];
434         } catch (e) {
435             JXG.debug("View3D " + object.id + ": Could not be removed: " + e);
436         }
437 
438         // this.update();
439 
440         this.board.removeObject(object, saveMethod);
441 
442         return this;
443     },
444 
445     /**
446      * Project 3D coordinates to 2D board coordinates
447      * The 3D coordinates are provides as three numbers x, y, z or one array of length 3.
448      *
449      * @param  {Number|Array} x
450      * @param  {Number[]} y
451      * @param  {Number[]} z
452      * @returns {Array} Array of length 3 containing the projection on to the board
453      * in homogeneous user coordinates.
454      */
455     project3DTo2D: function (x, y, z) {
456         var vec, w;
457         if (arguments.length === 3) {
458             vec = [1, x, y, z];
459         } else {
460             // Argument is an array
461             if (x.length === 3) {
462                 vec = [1].concat(x);
463             } else {
464                 vec = x;
465             }
466         }
467 
468         w = Mat.matVecMult(this.matrix3D, vec);
469 
470         if (this.projectionType === 'parallel') {
471             return w;
472         }
473 
474         // Central projection
475         w[1] /= w[0];
476         w[2] /= w[0];
477         w[3] /= w[0];
478         w[0] /= w[0];
479 
480         return Mat.matVecMult(this.viewPortTransform, w.slice(0, 3));
481     },
482 
483     /**
484      * Project a 2D coordinate to the plane defined by point "foot"
485      * and the normal vector `normal`.
486      *
487      * @param  {JXG.Point} point2d
488      * @param  {Array} normal
489      * @param  {Array} foot
490      * @returns {Array} of length 4 containing the projected
491      * point in homogeneous coordinates.
492      */
493     project2DTo3DPlane: function (point2d, normal, foot) {
494         var mat, rhs, d, le,
495             n = normal.slice(1),
496             sol = [1, 0, 0, 0];
497 
498         foot = foot || [1, 0, 0, 0];
499         le = Mat.norm(n, 3);
500         d = Mat.innerProduct(foot.slice(1), n, 3) / le;
501 
502         mat = this.matrix3D.slice(0, 3); // True copy
503         mat.push([0].concat(n));
504 
505         // 2D coordinates of point:
506         rhs = point2d.coords.usrCoords.concat([d]);
507         try {
508             // Prevent singularity in case elevation angle is zero
509             if (mat[2][3] === 1.0) {
510                 mat[2][1] = mat[2][2] = Mat.eps * 0.001;
511             }
512             sol = Mat.Numerics.Gauss(mat, rhs);
513         } catch (err) {
514             sol = [0, NaN, NaN, NaN];
515         }
516 
517         return sol;
518     },
519 
520     /**
521      * Project a 2D coordinate to a new 3D position by keeping
522      * the 3D x, y coordinates and changing only the z coordinate.
523      * All horizontal moves of the 2D point are ignored.
524      *
525      * @param {JXG.Point} point2d
526      * @param {Array} coords3D
527      * @returns {Array} of length 4 containing the projected
528      * point in homogeneous coordinates.
529      */
530     project2DTo3DVertical: function (point2d, coords3D) {
531         var m3D = this.matrix3D[2],
532             b = m3D[3],
533             rhs = point2d.coords.usrCoords[2]; // y in 2D
534 
535         rhs -= m3D[0] * m3D[0] + m3D[1] * coords3D[1] + m3D[2] * coords3D[2];
536         if (Math.abs(b) < Mat.eps) {
537             return coords3D; // No changes
538         } else {
539             return coords3D.slice(0, 3).concat([rhs / b]);
540         }
541     },
542 
543     /**
544      * Limit 3D coordinates to the bounding cube.
545      *
546      * @param {Array} c3d 3D coordinates [x,y,z]
547      * @returns Array with updated 3D coordinates.
548      */
549     project3DToCube: function (c3d) {
550         var cube = this.bbox3D;
551         if (c3d[1] < cube[0][0]) {
552             c3d[1] = cube[0][0];
553         }
554         if (c3d[1] > cube[0][1]) {
555             c3d[1] = cube[0][1];
556         }
557         if (c3d[2] < cube[1][0]) {
558             c3d[2] = cube[1][0];
559         }
560         if (c3d[2] > cube[1][1]) {
561             c3d[2] = cube[1][1];
562         }
563         if (c3d[3] < cube[2][0]) {
564             c3d[3] = cube[2][0];
565         }
566         if (c3d[3] > cube[2][1]) {
567             c3d[3] = cube[2][1];
568         }
569 
570         return c3d;
571     },
572 
573     /**
574      * Intersect a ray with the bounding cube of the 3D view.
575      * @param {Array} p 3D coordinates [x,y,z]
576      * @param {Array} d 3D direction vector of the line (array of length 3)
577      * @param {Number} r direction of the ray (positive if r > 0, negative if r < 0).
578      * @returns Affine ratio of the intersection of the line with the cube.
579      */
580     intersectionLineCube: function (p, d, r) {
581         var rnew, i, r0, r1;
582 
583         rnew = r;
584         for (i = 0; i < 3; i++) {
585             if (d[i] !== 0) {
586                 r0 = (this.bbox3D[i][0] - p[i]) / d[i];
587                 r1 = (this.bbox3D[i][1] - p[i]) / d[i];
588                 if (r < 0) {
589                     rnew = Math.max(rnew, Math.min(r0, r1));
590                 } else {
591                     rnew = Math.min(rnew, Math.max(r0, r1));
592                 }
593             }
594         }
595         return rnew;
596     },
597 
598     /**
599      * Test if coordinates are inside of the bounding cube.
600      * @param {array} q 3D coordinates [x,y,z] of a point.
601      * @returns Boolean
602      */
603     isInCube: function (q) {
604         return (
605             q[0] > this.bbox3D[0][0] - Mat.eps &&
606             q[0] < this.bbox3D[0][1] + Mat.eps &&
607             q[1] > this.bbox3D[1][0] - Mat.eps &&
608             q[1] < this.bbox3D[1][1] + Mat.eps &&
609             q[2] > this.bbox3D[2][0] - Mat.eps &&
610             q[2] < this.bbox3D[2][1] + Mat.eps
611         );
612     },
613 
614     /**
615      *
616      * @param {JXG.Plane3D} plane1
617      * @param {JXG.Plane3D} plane2
618      * @param {JXG.Plane3D} d
619      * @returns {Array} of length 2 containing the coordinates of the defining points of
620      * of the intersection segment.
621      */
622     intersectionPlanePlane: function (plane1, plane2, d) {
623         var ret = [[], []],
624             p,
625             dir,
626             r,
627             q;
628 
629         d = d || plane2.d;
630 
631         p = Mat.Geometry.meet3Planes(
632             plane1.normal,
633             plane1.d,
634             plane2.normal,
635             d,
636             Mat.crossProduct(plane1.normal, plane2.normal),
637             0
638         );
639         dir = Mat.Geometry.meetPlanePlane(
640             plane1.vec1,
641             plane1.vec2,
642             plane2.vec1,
643             plane2.vec2
644         );
645         r = this.intersectionLineCube(p, dir, Infinity);
646         q = Mat.axpy(r, dir, p);
647         if (this.isInCube(q)) {
648             ret[0] = q;
649         }
650         r = this.intersectionLineCube(p, dir, -Infinity);
651         q = Mat.axpy(r, dir, p);
652         if (this.isInCube(q)) {
653             ret[1] = q;
654         }
655         return ret;
656     },
657 
658     /**
659      * Generate mesh for a surface / plane.
660      * Returns array [dataX, dataY] for a JSXGraph curve's updateDataArray function.
661      * @param {Array|Function} func
662      * @param {Array} interval_u
663      * @param {Array} interval_v
664      * @returns Array
665      * @private
666      *
667      * @example
668      *  var el = view.create('curve', [[], []]);
669      *  el.updateDataArray = function () {
670      *      var steps_u = Type.evaluate(this.visProp.stepsu),
671      *           steps_v = Type.evaluate(this.visProp.stepsv),
672      *           r_u = Type.evaluate(this.range_u),
673      *           r_v = Type.evaluate(this.range_v),
674      *           func, ret;
675      *
676      *      if (this.F !== null) {
677      *          func = this.F;
678      *      } else {
679      *          func = [this.X, this.Y, this.Z];
680      *      }
681      *      ret = this.view.getMesh(func,
682      *          r_u.concat([steps_u]),
683      *          r_v.concat([steps_v]));
684      *
685      *      this.dataX = ret[0];
686      *      this.dataY = ret[1];
687      *  };
688      *
689      */
690     getMesh: function (func, interval_u, interval_v) {
691         var i_u, i_v, u, v,
692             c2d, delta_u, delta_v,
693             p = [0, 0, 0],
694             steps_u = interval_u[2],
695             steps_v = interval_v[2],
696             dataX = [],
697             dataY = [];
698 
699         delta_u = (Type.evaluate(interval_u[1]) - Type.evaluate(interval_u[0])) / steps_u;
700         delta_v = (Type.evaluate(interval_v[1]) - Type.evaluate(interval_v[0])) / steps_v;
701 
702         for (i_u = 0; i_u <= steps_u; i_u++) {
703             u = interval_u[0] + delta_u * i_u;
704             for (i_v = 0; i_v <= steps_v; i_v++) {
705                 v = interval_v[0] + delta_v * i_v;
706                 if (Type.isFunction(func)) {
707                     p = func(u, v);
708                 } else {
709                     p = [func[0](u, v), func[1](u, v), func[2](u, v)];
710                 }
711                 c2d = this.project3DTo2D(p);
712                 dataX.push(c2d[1]);
713                 dataY.push(c2d[2]);
714             }
715             dataX.push(NaN);
716             dataY.push(NaN);
717         }
718 
719         for (i_v = 0; i_v <= steps_v; i_v++) {
720             v = interval_v[0] + delta_v * i_v;
721             for (i_u = 0; i_u <= steps_u; i_u++) {
722                 u = interval_u[0] + delta_u * i_u;
723                 if (Type.isFunction(func)) {
724                     p = func(u, v);
725                 } else {
726                     p = [func[0](u, v), func[1](u, v), func[2](u, v)];
727                 }
728                 c2d = this.project3DTo2D(p);
729                 dataX.push(c2d[1]);
730                 dataY.push(c2d[2]);
731             }
732             dataX.push(NaN);
733             dataY.push(NaN);
734         }
735 
736         return [dataX, dataY];
737     },
738 
739     /**
740      *
741      */
742     animateAzimuth: function () {
743         var s = this.az_slide._smin,
744             e = this.az_slide._smax,
745             sdiff = e - s,
746             newVal = this.az_slide.Value() + 0.1;
747 
748         this.az_slide.position = (newVal - s) / sdiff;
749         if (this.az_slide.position > 1) {
750             this.az_slide.position = 0.0;
751         }
752         this.board.update();
753 
754         this.timeoutAzimuth = setTimeout(
755             function () {
756                 this.animateAzimuth();
757             }.bind(this),
758             200
759         );
760     },
761 
762     /**
763      *
764      */
765     stopAzimuth: function () {
766         clearTimeout(this.timeoutAzimuth);
767         this.timeoutAzimuth = null;
768     },
769 
770     /**
771      * Check if vertical dragging is enabled and which action is needed.
772      * Default is shiftKey.
773      *
774      * @returns Boolean
775      * @private
776      */
777     isVerticalDrag: function () {
778         var b = this.board,
779             key;
780         if (!Type.evaluate(this.visProp.verticaldrag.enabled)) {
781             return false;
782         }
783         key = '_' + Type.evaluate(this.visProp.verticaldrag.key) + 'Key';
784         return b[key];
785     }
786 });
787 
788 /**
789  * @class This element creates a 3D view.
790  * @pseudo
791  * @description  A View3D element provides the container and the methods to create and display 3D elements.
792  * It is contained in a JSXGraph board.
793  * @name View3D
794  * @augments JXG.View3D
795  * @constructor
796  * @type Object
797  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
798  * @param {Array_Array_Array} lower,dim,cube  Here, lower is an array of the form [x, y] and
799  * dim is an array of the form [w, h].
800  * The arrays [x, y] and [w, h] define the 2D frame into which the 3D cube is
801  * (roughly) projected. If the view's azimuth=0 and elevation=0, the 3D view will cover a rectangle with lower left corner
802  * [x,y] and side lengths [w, h] of the board.
803  * The array 'cube' is of the form [[x1, x2], [y1, y2], [z1, z2]]
804  * which determines the coordinate ranges of the 3D cube.
805  *
806  * @example
807  *  var bound = [-5, 5];
808  *  var view = board.create('view3d',
809  *      [[-6, -3],
810  *       [8, 8],
811  *       [bound, bound, bound]],
812  *      {
813  *          // Main axes
814  *          axesPosition: 'center',
815  *          xAxis: { strokeColor: 'blue', strokeWidth: 3},
816  *
817  *          // Planes
818  *          xPlaneRear: { fillColor: 'yellow',  mesh3d: {visible: false}},
819  *          yPlaneFront: { visible: true, fillColor: 'blue'},
820  *
821  *          // Axes on planes
822  *          xPlaneRearYAxis: {strokeColor: 'red'},
823  *          xPlaneRearZAxis: {strokeColor: 'red'},
824  *
825  *          yPlaneFrontXAxis: {strokeColor: 'blue'},
826  *          yPlaneFrontZAxis: {strokeColor: 'blue'},
827  *
828  *          zPlaneFrontXAxis: {visible: false},
829  *          zPlaneFrontYAxis: {visible: false}
830  *      });
831  *
832  * </pre><div id="JXGdd06d90e-be5d-4531-8f0b-65fc30b1a7c7" class="jxgbox" style="width: 500px; height: 500px;"></div>
833  * <script type="text/javascript">
834  *     (function() {
835  *         var board = JXG.JSXGraph.initBoard('JXGdd06d90e-be5d-4531-8f0b-65fc30b1a7c7',
836  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
837  *         var bound = [-5, 5];
838  *         var view = board.create('view3d',
839  *             [[-6, -3], [8, 8],
840  *             [bound, bound, bound]],
841  *             {
842  *                 // Main axes
843  *                 axesPosition: 'center',
844  *                 xAxis: { strokeColor: 'blue', strokeWidth: 3},
845  *                 // Planes
846  *                 xPlaneRear: { fillColor: 'yellow',  mesh3d: {visible: false}},
847  *                 yPlaneFront: { visible: true, fillColor: 'blue'},
848  *                 // Axes on planes
849  *                 xPlaneRearYAxis: {strokeColor: 'red'},
850  *                 xPlaneRearZAxis: {strokeColor: 'red'},
851  *                 yPlaneFrontXAxis: {strokeColor: 'blue'},
852  *                 yPlaneFrontZAxis: {strokeColor: 'blue'},
853  *                 zPlaneFrontXAxis: {visible: false},
854  *                 zPlaneFrontYAxis: {visible: false}
855  *             });
856  *     })();
857  *
858  * </script><pre>
859  *
860  */
861 JXG.createView3D = function (board, parents, attributes) {
862     var view, attr,
863         x, y, w, h,
864         coords = parents[0], // llft corner
865         size = parents[1]; // [w, h]
866 
867     attr = Type.copyAttributes(attributes, board.options, "view3d");
868     view = new JXG.View3D(board, parents, attr);
869     view.defaultAxes = view.create("axes3d", parents, attributes);
870 
871     x = coords[0];
872     y = coords[1];
873     w = size[0];
874     h = size[1];
875 
876     /**
877      * Slider to adapt azimuth angle
878      * @name JXG.View3D#az_slide
879      * @type {Slider}
880      */
881     view.az_slide = board.create(
882         "slider",
883         [
884             [x - 1, y - 2],
885             [x + w + 1, y - 2],
886             [0, 1.0, 2 * Math.PI]
887         ],
888         {
889             style: 6,
890             name: "az",
891             point1: { frozen: true },
892             point2: { frozen: true }
893         }
894     );
895 
896     /**
897      * Slider to adapt elevation angle
898      *
899      * @name JXG.View3D#el_slide
900      * @type {Slider}
901      */
902     view.el_slide = board.create(
903         "slider",
904         [
905             [x - 1, y],
906             [x - 1, y + h],
907             [0, 0.3, Math.PI / 2]
908         ],
909         {
910             style: 6,
911             name: "el",
912             point1: { frozen: true },
913             point2: { frozen: true }
914         }
915     );
916 
917     view.board.highlightInfobox = function (x, y, el) {
918         var d, i, c3d, foot,
919             pre = '<span style="color:black; font-size:200%">\u21C4  </span>',
920             brd = el.board,
921             arr, infobox,
922             p = null;
923 
924         if (view.isVerticalDrag()) {
925             pre = '<span style="color:black; font-size:200%">\u21C5  </span>';
926         }
927         // Search 3D parent
928         for (i = 0; i < el.parents.length; i++) {
929             p = brd.objects[el.parents[i]];
930             if (p.is3D) {
931                 break;
932             }
933         }
934         if (p) {
935             foot = [1, 0, 0, p.coords[3]];
936             c3d = view.project2DTo3DPlane(p.element2D, [1, 0, 0, 1], foot);
937             if (!view.isInCube(c3d)) {
938                 view.board.highlightCustomInfobox('', p);
939                 return;
940             }
941             d = Type.evaluate(p.visProp.infoboxdigits);
942             infobox = view.board.infobox;
943             if (d === 'auto') {
944                 if (infobox.useLocale()) {
945                     arr = [pre, '(', infobox.formatNumberLocale(p.X()), ' | ', infobox.formatNumberLocale(p.Y()), ' | ', infobox.formatNumberLocale(p.Z()), ')'];
946                 } else {
947                     arr = [pre, '(', Type.autoDigits(p.X()), ' | ', Type.autoDigits(p.Y()), ' | ', Type.autoDigits(p.Z()), ')'];
948                 }
949 
950             } else {
951                 if (infobox.useLocale()) {
952                     arr = [pre, '(', infobox.formatNumberLocale(p.X(), d), ' | ', infobox.formatNumberLocale(p.Y(), d), ' | ', infobox.formatNumberLocale(p.Z(), d), ')'];
953                 } else {
954                     arr = [pre, '(', Type.toFixed(p.X(), d), ' | ', Type.toFixed(p.Y(), d), ' | ', Type.toFixed(p.Z(), d), ')'];
955                 }
956             }
957             view.board.highlightCustomInfobox(arr.join(''), p);
958         } else {
959             view.board.highlightCustomInfobox('(' + x + ', ' + y + ')', el);
960         }
961     };
962 
963     view.board.update();
964 
965     return view;
966 };
967 
968 JXG.registerElement("view3d", JXG.createView3D);
969 
970 export default JXG.View3D;
971