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      * 3D-to-2D transformation matrix
 84      * @type  {Array} 3 x 4 matrix
 85      * @private
 86      */
 87     this.matrix3D = [
 88         [1, 0, 0, 0],
 89         [0, 1, 0, 0],
 90         [0, 0, 1, 0]
 91     ];
 92 
 93     /**
 94      * Lower left corner [x, y] of the 3D view if elevation and azimuth are set to 0.
 95      * @type array
 96      * @private
 97      */
 98     this.llftCorner = parents[0];
 99 
100     /**
101      * Width and height [w, h] of the 3D view if elevation and azimuth are set to 0.
102      * @type array
103      * @private
104      */
105     this.size = parents[1];
106 
107     /**
108      * Bounding box (cube) [[x1, x2], [y1,y2], [z1,z2]] of the 3D view
109      * @type array
110      */
111     this.bbox3D = parents[2];
112 
113     /**
114      * Distance of the view to the origin. In other words, its
115      * the radius of the sphere where the camera sits.view.board.update
116      * @type Number
117      */
118     this.r = -1;
119 
120     this.timeoutAzimuth = null;
121 
122     this.id = this.board.setId(this, "V");
123     this.board.finalizeAdding(this);
124     this.elType = "view3d";
125 
126     this.methodMap = Type.deepCopy(this.methodMap, {
127         // TODO
128     });
129 };
130 JXG.View3D.prototype = new GeometryElement();
131 
132 JXG.extend(
133     JXG.View3D.prototype, /** @lends JXG.View3D.prototype */ {
134 
135     /**
136      * Creates a new 3D element of type elementType.
137      * @param {String} elementType Type of the element to be constructed given as a string e.g. 'point3d' or 'surface3d'.
138      * @param {Array} parents Array of parent elements needed to construct the element e.g. coordinates for a 3D point or two
139      * 3D points to construct a line. This highly depends on the elementType that is constructed. See the corresponding JXG.create*
140      * methods for a list of possible parameters.
141      * @param {Object} [attributes] An object containing the attributes to be set. This also depends on the elementType.
142      * Common attributes are name, visible, strokeColor.
143      * @returns {Object} Reference to the created element. This is usually a GeometryElement3D, but can be an array containing
144      * two or more elements.
145      */
146     create: function (elementType, parents, attributes) {
147         var prefix = [],
148             // is3D = false,
149             el;
150 
151         if (elementType.indexOf("3d") > 0) {
152             // is3D = true;
153             prefix.push(this);
154         }
155         el = this.board.create(elementType, prefix.concat(parents), attributes);
156 
157         return el;
158     },
159 
160     /**
161      * Select a single or multiple elements at once.
162      * @param {String|Object|function} str The name, id or a reference to a JSXGraph 3D element in the 3D view. An object will
163      * be used as a filter to return multiple elements at once filtered by the properties of the object.
164      * @param {Boolean} onlyByIdOrName If true (default:false) elements are only filtered by their id, name or groupId.
165      * The advanced filters consisting of objects or functions are ignored.
166      * @returns {JXG.GeometryElement3D|JXG.Composition}
167      * @example
168      * // select the element with name A
169      * view.select('A');
170      *
171      * // select all elements with strokecolor set to 'red' (but not '#ff0000')
172      * view.select({
173      *   strokeColor: 'red'
174      * });
175      *
176      * // select all points on or below the x/y plane and make them black.
177      * view.select({
178      *   elType: 'point3d',
179      *   Z: function (v) {
180      *     return v <= 0;
181      *   }
182      * }).setAttribute({color: 'black'});
183      *
184      * // select all elements
185      * view.select(function (el) {
186      *   return true;
187      * });
188      */
189     select: function (str, onlyByIdOrName) {
190         var flist,
191             olist,
192             i,
193             l,
194             s = str;
195 
196         if (s === null) {
197             return s;
198         }
199 
200         // It's a string, most likely an id or a name.
201         if (Type.isString(s) && s !== "") {
202             // Search by ID
203             if (Type.exists(this.objects[s])) {
204                 s = this.objects[s];
205                 // Search by name
206             } else if (Type.exists(this.elementsByName[s])) {
207                 s = this.elementsByName[s];
208                 // // Search by group ID
209                 // } else if (Type.exists(this.groups[s])) {
210                 //     s = this.groups[s];
211             }
212 
213             // It's a function or an object, but not an element
214         } else if (
215             !onlyByIdOrName &&
216             (Type.isFunction(s) || (Type.isObject(s) && !Type.isFunction(s.setAttribute)))
217         ) {
218             flist = Type.filterElements(this.objectsList, s);
219 
220             olist = {};
221             l = flist.length;
222             for (i = 0; i < l; i++) {
223                 olist[flist[i].id] = flist[i];
224             }
225             s = new Composition(olist);
226 
227             // It's an element which has been deleted (and still hangs around, e.g. in an attractor list
228         } else if (
229             Type.isObject(s) &&
230             Type.exists(s.id) &&
231             !Type.exists(this.objects[s.id])
232         ) {
233             s = null;
234         }
235 
236         return s;
237     },
238 
239     update: function () {
240         // Update 3D-to-2D transformation matrix with the actual
241         // elevation and azimuth angles.
242 
243         var e, r, a, f, mat, shift;
244 
245         if (
246             !Type.exists(this.el_slide) ||
247             !Type.exists(this.az_slide) ||
248             !this.needsUpdate
249         ) {
250             return this;
251         }
252 
253         e = this.el_slide.Value();
254         r = this.r;
255         a = this.az_slide.Value();
256         f = r * Math.sin(e);
257         mat = [
258             [1, 0, 0],
259             [0, 1, 0],
260             [0, 0, 1]
261         ];
262 
263         // Rotate the scenery around the center of the box,
264         // not around the origin
265         shift = [
266             [1, 0, 0, 0],
267             [-0.5 * (this.bbox3D[0][0] + this.bbox3D[0][1]), 1, 0, 0],
268             [-0.5 * (this.bbox3D[1][0] + this.bbox3D[1][1]), 0, 1, 0],
269             [-0.5 * (this.bbox3D[2][0] + this.bbox3D[2][1]), 0, 0, 1]
270         ];
271 
272         // matrix3D projects homogeneous 3D coords in the View3D
273         // to homogeneous 2D coordinates in the board
274         this.matrix3D = [
275             [1, 0, 0, 0],
276             [0, 1, 0, 0],
277             [0, 0, 1, 0]
278         ];
279 
280         this.matrix3D[1][1] = r * Math.cos(a);
281         this.matrix3D[1][2] = -r * Math.sin(a);
282         this.matrix3D[2][1] = f * Math.sin(a);
283         this.matrix3D[2][2] = f * Math.cos(a);
284         this.matrix3D[2][3] = Math.cos(e);
285 
286         // Add a second transformation to scale and shift the projection
287         // on the board
288         mat[1][1] = this.size[0] / (this.bbox3D[0][1] - this.bbox3D[0][0]); // w / d_x
289         mat[2][2] = this.size[1] / (this.bbox3D[1][1] - this.bbox3D[1][0]); // h / d_y
290         mat[1][0] = this.llftCorner[0] + mat[1][1] * 0.5 * (this.bbox3D[0][1] - this.bbox3D[0][0]); // llft_x
291         mat[2][0] = this.llftCorner[1] + mat[2][2] * 0.5 * (this.bbox3D[1][1] - this.bbox3D[1][0]); // llft_y
292 
293         // Combine the two projections
294         this.matrix3D = Mat.matMatMult(mat,
295             // Mat.matMatMult(shift2,
296                 Mat.matMatMult(this.matrix3D, shift)
297             //)
298         );
299 
300         return this;
301     },
302 
303     updateRenderer: function () {
304         this.needsUpdate = false;
305         return this;
306     },
307 
308     removeObject: function(object, saveMethod) {
309         var i;
310 
311         // this.board.removeObject(object, saveMethod);
312         if (Type.isArray(object)) {
313             for (i = 0; i < object.length; i++) {
314                 this.removeObject(object[i]);
315             }
316             return this;
317         }
318 
319         object = this.select(object);
320 
321         // // If the object which is about to be removed unknown or a string, do nothing.
322         // // it is a string if a string was given and could not be resolved to an element.
323         if (!Type.exists(object) || Type.isString(object)) {
324             return this;
325         }
326 
327         try {
328         //     // remove all children.
329         //     for (el in object.childElements) {
330         //         if (object.childElements.hasOwnProperty(el)) {
331         //             object.childElements[el].board.removeObject(object.childElements[el]);
332         //         }
333         //     }
334 
335             delete this.objects[object.id];
336         } catch (e) {
337             JXG.debug("View3D " + object.id + ": Could not be removed: " + e);
338         }
339 
340         // this.update();
341 
342         this.board.removeObject(object, saveMethod);
343 
344         return this;
345     },
346 
347     /**
348      * Project 3D coordinates to 2D board coordinates
349      * The 3D coordinates are provides as three numbers x, y, z or one array of length 3.
350      *
351      * @param  {Number|Array} x
352      * @param  {[Number]} y
353      * @param  {[Number]} z
354      * @returns {Array} Array of length 3 containing the projection on to the board
355      * in homogeneous user coordinates.
356      */
357     project3DTo2D: function (x, y, z) {
358         var vec;
359         if (arguments.length === 3) {
360             vec = [1, x, y, z];
361         } else {
362             // Argument is an array
363             if (x.length === 3) {
364                 vec = [1].concat(x);
365             } else {
366                 vec = x;
367             }
368         }
369         return Mat.matVecMult(this.matrix3D, vec);
370     },
371 
372     /**
373      * Project a 2D coordinate to the plane defined by the point foot
374      * and the normal vector `normal`.
375      *
376      * @param  {JXG.Point} point2d
377      * @param  {Array} normal
378      * @param  {Array} foot
379      * @returns {Array} of length 4 containing the projected
380      * point in homogeneous coordinates.
381      */
382     project2DTo3DPlane: function (point2d, normal, foot) {
383         var mat,
384             rhs,
385             d,
386             le,
387             n = normal.slice(1),
388             sol = [1, 0, 0, 0];
389 
390         foot = foot || [1, 0, 0, 0];
391         le = Mat.norm(n, 3);
392         d = Mat.innerProduct(foot.slice(1), n, 3) / le;
393 
394         mat = this.matrix3D.slice(0, 3); // True copy
395         mat.push([0].concat(n));
396 
397         // 2D coordinates of point:
398         rhs = point2d.coords.usrCoords.concat([d]);
399         try {
400             // Prevent singularity in case elevation angle is zero
401             if (mat[2][3] === 1.0) {
402                 mat[2][1] = mat[2][2] = Mat.eps * 0.001;
403             }
404             sol = Mat.Numerics.Gauss(mat, rhs);
405         } catch (err) {
406             sol = [0, NaN, NaN, NaN];
407         }
408 
409         return sol;
410     },
411 
412     /**
413      * Project a 2D coordinate to a new 3D position by keeping
414      * the 3D x, y coordinates and changing only the z coordinate.
415      * All horizontal moves of the 2D point are ignored.
416      *
417      * @param {JXG.Point} point2d
418      * @param {Array} coords3D
419      * @returns {Array} of length 4 containing the projected
420      * point in homogeneous coordinates.
421      */
422     project2DTo3DVertical: function (point2d, coords3D) {
423         var m3D = this.matrix3D[2],
424             b = m3D[3],
425             rhs = point2d.coords.usrCoords[2]; // y in 2D
426 
427         rhs -= m3D[0] * m3D[0] + m3D[1] * coords3D[1] + m3D[2] * coords3D[2];
428         if (Math.abs(b) < Mat.eps) {
429             return coords3D; // No changes
430         } else {
431             return coords3D.slice(0, 3).concat([rhs / b]);
432         }
433     },
434 
435     /**
436      * Limit 3D coordinates to the bounding cube.
437      *
438      * @param {Array} c3d 3D coordinates [x,y,z]
439      * @returns Array with updated 3D coordinates.
440      */
441     project3DToCube: function (c3d) {
442         var cube = this.bbox3D;
443         if (c3d[1] < cube[0][0]) {
444             c3d[1] = cube[0][0];
445         }
446         if (c3d[1] > cube[0][1]) {
447             c3d[1] = cube[0][1];
448         }
449         if (c3d[2] < cube[1][0]) {
450             c3d[2] = cube[1][0];
451         }
452         if (c3d[2] > cube[1][1]) {
453             c3d[2] = cube[1][1];
454         }
455         if (c3d[3] < cube[2][0]) {
456             c3d[3] = cube[2][0];
457         }
458         if (c3d[3] > cube[2][1]) {
459             c3d[3] = cube[2][1];
460         }
461 
462         return c3d;
463     },
464 
465     /**
466      * Intersect a ray with the bounding cube of the 3D view.
467      * @param {Array} p 3D coordinates [x,y,z]
468      * @param {Array} d 3D direction vector of the line (array of length 3)
469      * @param {Number} r direction of the ray (positive if r > 0, negative if r < 0).
470      * @returns Affine ratio of the intersection of the line with the cube.
471      */
472     intersectionLineCube: function (p, d, r) {
473         var rnew, i, r0, r1;
474 
475         rnew = r;
476         for (i = 0; i < 3; i++) {
477             if (d[i] !== 0) {
478                 r0 = (this.bbox3D[i][0] - p[i]) / d[i];
479                 r1 = (this.bbox3D[i][1] - p[i]) / d[i];
480                 if (r < 0) {
481                     rnew = Math.max(rnew, Math.min(r0, r1));
482                 } else {
483                     rnew = Math.min(rnew, Math.max(r0, r1));
484                 }
485             }
486         }
487         return rnew;
488     },
489 
490     /**
491      * Test if coordinates are inside of the bounding cube.
492      * @param {array} q 3D coordinates [x,y,z] of a point.
493      * @returns Boolean
494      */
495     isInCube: function (q) {
496         return (
497             q[0] > this.bbox3D[0][0] - Mat.eps &&
498             q[0] < this.bbox3D[0][1] + Mat.eps &&
499             q[1] > this.bbox3D[1][0] - Mat.eps &&
500             q[1] < this.bbox3D[1][1] + Mat.eps &&
501             q[2] > this.bbox3D[2][0] - Mat.eps &&
502             q[2] < this.bbox3D[2][1] + Mat.eps
503         );
504     },
505 
506     /**
507      *
508      * @param {JXG.Plane3D} plane1
509      * @param {JXG.Plane3D} plane2
510      * @param {JXG.Plane3D} d
511      * @returns {Array} of length 2 containing the coordinates of the defining points of
512      * of the intersection segment.
513      */
514     intersectionPlanePlane: function (plane1, plane2, d) {
515         var ret = [[], []],
516             p,
517             dir,
518             r,
519             q;
520 
521         d = d || plane2.d;
522 
523         p = Mat.Geometry.meet3Planes(
524             plane1.normal,
525             plane1.d,
526             plane2.normal,
527             d,
528             Mat.crossProduct(plane1.normal, plane2.normal),
529             0
530         );
531         dir = Mat.Geometry.meetPlanePlane(
532             plane1.vec1,
533             plane1.vec2,
534             plane2.vec1,
535             plane2.vec2
536         );
537         r = this.intersectionLineCube(p, dir, Infinity);
538         q = Mat.axpy(r, dir, p);
539         if (this.isInCube(q)) {
540             ret[0] = q;
541         }
542         r = this.intersectionLineCube(p, dir, -Infinity);
543         q = Mat.axpy(r, dir, p);
544         if (this.isInCube(q)) {
545             ret[1] = q;
546         }
547         return ret;
548     },
549 
550     /**
551      * Generate mesh for a surface / plane.
552      * Returns array [dataX, dataY] for a JSXGraph curve's updateDataArray function.
553      * @param {Array,Function} func
554      * @param {Array} interval_u
555      * @param {Array} interval_v
556      * @returns Array
557      * @private
558      *
559      * @example
560      *  var el = view.create('curve', [[], []]);
561      *  el.updateDataArray = function () {
562      *      var steps_u = Type.evaluate(this.visProp.stepsu),
563      *           steps_v = Type.evaluate(this.visProp.stepsv),
564      *           r_u = Type.evaluate(this.range_u),
565      *           r_v = Type.evaluate(this.range_v),
566      *           func, ret;
567      *
568      *      if (this.F !== null) {
569      *          func = this.F;
570      *      } else {
571      *          func = [this.X, this.Y, this.Z];
572      *      }
573      *      ret = this.view.getMesh(func,
574      *          r_u.concat([steps_u]),
575      *          r_v.concat([steps_v]));
576      *
577      *      this.dataX = ret[0];
578      *      this.dataY = ret[1];
579      *  };
580      *
581      */
582     getMesh: function (func, interval_u, interval_v) {
583         var i_u, i_v, u, v,
584             c2d, delta_u, delta_v,
585             p = [0, 0, 0],
586             steps_u = interval_u[2],
587             steps_v = interval_v[2],
588             dataX = [],
589             dataY = [];
590 
591         delta_u = (Type.evaluate(interval_u[1]) - Type.evaluate(interval_u[0])) / steps_u;
592         delta_v = (Type.evaluate(interval_v[1]) - Type.evaluate(interval_v[0])) / steps_v;
593 
594         for (i_u = 0; i_u <= steps_u; i_u++) {
595             u = interval_u[0] + delta_u * i_u;
596             for (i_v = 0; i_v <= steps_v; i_v++) {
597                 v = interval_v[0] + delta_v * i_v;
598                 if (Type.isFunction(func)) {
599                     p = func(u, v);
600                 } else {
601                     p = [func[0](u, v), func[1](u, v), func[2](u, v)];
602                 }
603                 c2d = this.project3DTo2D(p);
604                 dataX.push(c2d[1]);
605                 dataY.push(c2d[2]);
606             }
607             dataX.push(NaN);
608             dataY.push(NaN);
609         }
610 
611         for (i_v = 0; i_v <= steps_v; i_v++) {
612             v = interval_v[0] + delta_v * i_v;
613             for (i_u = 0; i_u <= steps_u; i_u++) {
614                 u = interval_u[0] + delta_u * i_u;
615                 if (Type.isFunction(func)) {
616                     p = func(u, v);
617                 } else {
618                     p = [func[0](u, v), func[1](u, v), func[2](u, v)];
619                 }
620                 c2d = this.project3DTo2D(p);
621                 dataX.push(c2d[1]);
622                 dataY.push(c2d[2]);
623             }
624             dataX.push(NaN);
625             dataY.push(NaN);
626         }
627 
628         return [dataX, dataY];
629     },
630 
631     /**
632      *
633      */
634     animateAzimuth: function () {
635         var s = this.az_slide._smin,
636             e = this.az_slide._smax,
637             sdiff = e - s,
638             newVal = this.az_slide.Value() + 0.1;
639 
640         this.az_slide.position = (newVal - s) / sdiff;
641         if (this.az_slide.position > 1) {
642             this.az_slide.position = 0.0;
643         }
644         this.board.update();
645 
646         this.timeoutAzimuth = setTimeout(
647             function () {
648                 this.animateAzimuth();
649             }.bind(this),
650             200
651         );
652     },
653 
654     /**
655      *
656      */
657     stopAzimuth: function () {
658         clearTimeout(this.timeoutAzimuth);
659         this.timeoutAzimuth = null;
660     },
661 
662     /**
663      * Check if vertical dragging is enabled and which action is needed.
664      * Default is shiftKey.
665      *
666      * @returns Boolean
667      * @private
668      */
669     isVerticalDrag: function () {
670         var b = this.board,
671             key;
672         if (!Type.evaluate(this.visProp.verticaldrag.enabled)) {
673             return false;
674         }
675         key = '_' + Type.evaluate(this.visProp.verticaldrag.key) + 'Key';
676         return b[key];
677     }
678 });
679 
680 /**
681  * @class This element creates a 3D view.
682  * @pseudo
683  * @description  A View3D element provides the container and the methods to create and display 3D elements.
684  * It is contained in a JSXGraph board.
685  * @name View3D
686  * @augments JXG.View3D
687  * @constructor
688  * @type Object
689  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
690  * @param {Array_Array_Array} lower,dim,cube  Here, lower is an array of the form [x, y] and
691  * dim is an array of the form [w, h].
692  * The arrays [x, y] and [w, h] define the 2D frame into which the 3D cube is
693  * (roughly) projected. If the view's azimuth=0 and elevation=0, the 3D view will cover a rectangle with lower left corner
694  * [x,y] and side lengths [w, h] of the board.
695  * The array 'cube' is of the form [[x1, x2], [y1, y2], [z1, z2]]
696  * which determines the coordinate ranges of the 3D cube.
697  *
698  * @example
699  *  var bound = [-5, 5];
700  *  var view = board.create('view3d',
701  *      [[-6, -3],
702  *       [8, 8],
703  *       [bound, bound, bound]],
704  *      {
705  *          // Main axes
706  *          axesPosition: 'center',
707  *          xAxis: { strokeColor: 'blue', strokeWidth: 3},
708  *
709  *          // Planes
710  *          xPlaneRear: { fillColor: 'yellow',  mesh3d: {visible: false}},
711  *          yPlaneFront: { visible: true, fillColor: 'blue'},
712  *
713  *          // Axes on planes
714  *          xPlaneRearYAxis: {strokeColor: 'red'},
715  *          xPlaneRearZAxis: {strokeColor: 'red'},
716  *
717  *          yPlaneFrontXAxis: {strokeColor: 'blue'},
718  *          yPlaneFrontZAxis: {strokeColor: 'blue'},
719  *
720  *          zPlaneFrontXAxis: {visible: false},
721  *          zPlaneFrontYAxis: {visible: false}
722  *      });
723  *
724  * </pre><div id="JXGdd06d90e-be5d-4531-8f0b-65fc30b1a7c7" class="jxgbox" style="width: 500px; height: 500px;"></div>
725  * <script type="text/javascript">
726  *     (function() {
727  *         var board = JXG.JSXGraph.initBoard('JXGdd06d90e-be5d-4531-8f0b-65fc30b1a7c7',
728  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
729  *         var bound = [-5, 5];
730  *         var view = board.create('view3d',
731  *             [[-6, -3], [8, 8],
732  *             [bound, bound, bound]],
733  *             {
734  *                 // Main axes
735  *                 axesPosition: 'center',
736  *                 xAxis: { strokeColor: 'blue', strokeWidth: 3},
737  *                 // Planes
738  *                 xPlaneRear: { fillColor: 'yellow',  mesh3d: {visible: false}},
739  *                 yPlaneFront: { visible: true, fillColor: 'blue'},
740  *                 // Axes on planes
741  *                 xPlaneRearYAxis: {strokeColor: 'red'},
742  *                 xPlaneRearZAxis: {strokeColor: 'red'},
743  *                 yPlaneFrontXAxis: {strokeColor: 'blue'},
744  *                 yPlaneFrontZAxis: {strokeColor: 'blue'},
745  *                 zPlaneFrontXAxis: {visible: false},
746  *                 zPlaneFrontYAxis: {visible: false}
747  *             });
748  *     })();
749  *
750  * </script><pre>
751  *
752  */
753 JXG.createView3D = function (board, parents, attributes) {
754     var view, attr,
755         x, y, w, h,
756         coords = parents[0], // llft corner
757         size = parents[1]; // [w, h]
758 
759     attr = Type.copyAttributes(attributes, board.options, "view3d");
760     view = new JXG.View3D(board, parents, attr);
761     view.defaultAxes = view.create("axes3d", parents, attributes);
762 
763     x = coords[0];
764     y = coords[1];
765     w = size[0];
766     h = size[1];
767 
768     /**
769      * Slider to adapt azimuth angle
770      * @name JXG.View3D#az_slide
771      * @type {Slider}
772      */
773     view.az_slide = board.create(
774         "slider",
775         [
776             [x - 1, y - 2],
777             [x + w + 1, y - 2],
778             [0, 1.0, 2 * Math.PI]
779         ],
780         {
781             style: 6,
782             name: "az",
783             point1: { frozen: true },
784             point2: { frozen: true }
785         }
786     );
787 
788     /**
789      * Slider to adapt elevation angle
790      *
791      * @name JXG.View3D#el_slide
792      * @type {Slider}
793      */
794     view.el_slide = board.create(
795         "slider",
796         [
797             [x - 1, y],
798             [x - 1, y + h],
799             [0, 0.3, Math.PI / 2]
800         ],
801         {
802             style: 6,
803             name: "el",
804             point1: { frozen: true },
805             point2: { frozen: true }
806         }
807     );
808 
809     view.board.highlightInfobox = function (x, y, el) {
810         var d, i, c3d, foot,
811             pre = '<span style="color:black; font-size:200%">\u21C4  </span>',
812             brd = el.board,
813             arr, infobox,
814             p = null;
815 
816         if (view.isVerticalDrag()) {
817             pre = '<span style="color:black; font-size:200%">\u21C5  </span>';
818         }
819         // Search 3D parent
820         for (i = 0; i < el.parents.length; i++) {
821             p = brd.objects[el.parents[i]];
822             if (p.is3D) {
823                 break;
824             }
825         }
826         if (p) {
827             foot = [1, 0, 0, p.coords[3]];
828             c3d = view.project2DTo3DPlane(p.element2D, [1, 0, 0, 1], foot);
829             if (!view.isInCube(c3d)) {
830                 view.board.highlightCustomInfobox('', p);
831                 return;
832             }
833             d = Type.evaluate(p.visProp.infoboxdigits);
834             infobox = view.board.infobox;
835             console.log(infobox.useLocale())
836             if (d === 'auto') {
837                 if (infobox.useLocale()) {
838                     arr = [pre, '(', infobox.formatNumberLocale(p.X()), ' | ', infobox.formatNumberLocale(p.Y()), ' | ', infobox.formatNumberLocale(p.Z()), ')']
839                 } else {
840                     arr = [pre, '(', Type.autoDigits(p.X()), ' | ', Type.autoDigits(p.Y()), ' | ', Type.autoDigits(p.Z()), ')']
841                 }
842 
843             } else {
844                 if (infobox.useLocale()) {
845                     arr = [pre, '(', infobox.formatNumberLocale(p.X(), d), ' | ', infobox.formatNumberLocale(p.Y(), d), ' | ', infobox.formatNumberLocale(p.Z(), d), ')'];
846                 } else {
847                     arr = [pre, '(', Type.toFixed(p.X(), d), ' | ', Type.toFixed(p.Y(), d), ' | ', Type.toFixed(p.Z(), d), ')'];
848                 }
849             }
850             view.board.highlightCustomInfobox(arr.join(''), p);
851         } else {
852             view.board.highlightCustomInfobox('(' + x + ', ' + y + ')', el);
853         }
854     };
855 
856     view.board.update();
857 
858     return view;
859 };
860 
861 JXG.registerElement("view3d", JXG.createView3D);
862 
863 export default JXG.View3D;
864