      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