      1 /*
      2     Copyright 2008-2025
      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.js";
     32 import Const from "../base/constants.js";
     33 import Mat from "../math/math.js";
     34 import Geometry from "../math/geometry.js";
     35 import Type from "../utils/type.js";
     36
     37 /**
     38  * Constructor for 3D surfaces.
     39  * @class Creates a new 3D surface object. Do not use this constructor to create a 3D surface. Use {@link JXG.View3D#create} with type {@link Surface3D} instead.
     40  *
     41  * @augments JXG.GeometryElement3D
     42  * @augments JXG.GeometryElement
     43  * @param {View3D} view
     44  * @param {Function} F
     45  * @param {Function} X
     46  * @param {Function} Y
     47  * @param {Function} Z
     48  * @param {Array} range_u
     49  * @param {Array} range_v
     50  * @param {Object} attributes
     51  * @see JXG.Board#generateName
     52  */
     53 JXG.Surface3D = function (view, F, X, Y, Z, range_u, range_v, attributes) {
     54     this.constructor(
     55         view.board,
     56         attributes,
     57         Const.OBJECT_TYPE_SURFACE3D,
     58         Const.OBJECT_CLASS_3D
     59     );
     60     this.constructor3D(view, "surface3d");
     61
     62     this.board.finalizeAdding(this);
     63
     64     /**
     65      * Internal function defining the surface
     66      * without applying any transformations.
     67      *
     68      * @function
     69      * @param {Number} u
     70      * @param {Number} v
     71      * @returns Array [x, y, z] of length 3
     72      * @private
     73      */
     74     this._F = F;
     75
     76     /**
     77      * Internal function which maps (u, v) to x; i.e. it defines the x-coordinate of the surface
     78      * without applying any transformations.
     79      * @function
     80      * @param {Number} u
     81      * @param {Number} v
     82      * @returns Number
     83      * @private
     84      */
     85     this._X = X;
     86
     87     /**
     88      * Internal function which maps (u, v) to y; i.e. it defines the y-coordinate of the surface
     89      * without applying any transformations.
     90      * @function
     91      * @param {Number} u
     92      * @param {Number} v
     93      * @returns Number
     94      * @private
     95      */
     96     this._Y = Y;
     97
     98     /**
     99      * Internal function which maps (u, v) to z; i.e. it defines the z-coordinate of the surface
    100      * without applying any transformations.
    101      * @function
    102      * @param {Number} u
    103      * @param {Number} v
    104      * @returns Number
    105      * @private
    106      */
    107     this._Z = Z;
    108
    109     if (this._F !== null) {
    110         this._X = function (u, v) {
    111             return this._F(u, v)[0];
    112         };
    113         this._Y = function (u, v) {
    114             return this._F(u, v)[1];
    115         };
    116         this._Z = function (u, v) {
    117             return this._F(u, v)[2];
    118         };
    119     } else {
    120         if (this._X !== null) {
    121             this._F = function(u, v) {
    122                 return [this._X(u, v), this._Y(u, v), this._Z(u, v)];
    123             };
    124         }
    125     }
    126
    127     this.range_u = range_u;
    128     this.range_v = range_v;
    129
    130     this.dataX = null;
    131     this.dataY = null;
    132     this.dataZ = null;
    133     this.points = [];
    134
    135     this.methodMap = Type.deepCopy(this.methodMap, {
    136         // TODO
    137     });
    138 };
    139 JXG.Surface3D.prototype = new JXG.GeometryElement();
    140 Type.copyPrototypeMethods(JXG.Surface3D, JXG.GeometryElement3D, "constructor3D");
    141
    142 JXG.extend(
    143     JXG.Surface3D.prototype,
    144     /** @lends JXG.Surface3D.prototype */ {
    145
    146         updateWireframe: function () {
    147             var steps_u, steps_v,
    148                 i_u, i_v,
    149                 r_u, r_v,
    150                 s_u, s_v,
    151                 e_u, e_v,
    152                 delta_u, delta_v,
    153                 u, v,
    154                 c3d = [1, 0, 0, 0];
    155
    156             this.points = [];
    157
    158             steps_u = this.evalVisProp('stepsu');
    159             steps_v = this.evalVisProp('stepsv');
    160             r_u = Type.evaluate(this.range_u);
    161             r_v = Type.evaluate(this.range_v);
    162             s_u = Type.evaluate(r_u[0]);
    163             s_v = Type.evaluate(r_v[0]);
    164             e_u = Type.evaluate(r_u[1]);
    165             e_v = Type.evaluate(r_v[1]);
    166             delta_u = (e_u - s_u) / (steps_u);
    167             delta_v = (e_v - s_v) / (steps_v);
    168
    169             for (i_u = 0, u = s_u; i_u <= steps_u; i_u++, u += delta_u) {
    170                 this.points.push([]);
    171                 for (i_v = 0, v = s_v; i_v <= steps_v; i_v++, v += delta_v) {
    172                     c3d = this.F(u, v);
    173                     c3d.unshift(1);
    174                     this.points[i_u].push(c3d);
    175                 }
    176             }
    177
    178             return this;
    179         },
    180
    181         updateCoords: function () {
    182             if (this._F !== null) {
    183                 this.updateWireframe();
    184             } else {
    185                 this.updateTransform();
    186             }
    187             return this;
    188         },
    189
    190         /**
    191          * Generic function which evaluates the function term of the surface
    192          * and applies its transformations.
    193          * @param {Number} u
    194          * @param {Number} v
    195          * @returns
    196          */
    197         evalF: function(u, v) {
    198             var t, i,
    199                 c3d = [0, 0, 0, 0];
    200
    201             if (this.transformations.length === 0 || !Type.exists(this.baseElement)) {
    202                 c3d = this._F(u, v);
    203                 return c3d;
    204             }
    205
    206             t = this.transformations;
    207             for (i = 0; i < t.length; i++) {
    208                 t[i].update();
    209             }
    210
    211             if (this === this.baseElement) {
    212                 c3d = this._F(u, v);
    213             } else {
    214                 c3d = this.baseElement.evalF(u, v);
    215             }
    216             c3d.unshift(1);
    217             c3d = Mat.matVecMult(t[0].matrix, c3d);
    218             for (i = 1; i < t.length; i++) {
    219                 c3d = Mat.matVecMult(t[i].matrix, c3d);
    220             }
    221
    222             return c3d.slice(1);
    223         },
    224
    225         /**
    226          * Function defining the surface plus applying transformations.
    227          * @param {Number} u
    228          * @param {Number} v
    229         * @returns Array [x, y, z] of length 3
    230          */
    231         F: function(u, v) {
    232             return this.evalF(u, v);
    233         },
    234
    235         /**
    236         * Function which maps (u, v) to z; i.e. it defines the x-coordinate of the surface
    237         * plus applying transformations.
    238         * @param {Number} u
    239         * @param {Number} v
    240         * @returns Number
    241         */
    242         X: function(u, v) {
    243             return this.evalF(u, v)[0];
    244         },
    245
    246         /**
    247         * Function which maps (u, v) to y; i.e. it defines the y-coordinate of the surface
    248         * plus applying transformations.
    249         * @param {Number} u
    250         * @param {Number} v
    251         * @returns Number
    252         */
    253         Y: function(u, v) {
    254             return this.evalF(u, v)[1];
    255         },
    256
    257         /**
    258         * Function which maps (u, v) to z; i.e. it defines the z-coordinate of the surface
    259         * plus applying transformations.
    260         * @param {Number} u
    261         * @param {Number} v
    262         * @returns Number
    263         */
    264         Z: function(u, v) {
    265             return this.evalF(u, v)[2];
    266         },
    267
    268         /**
    269          * @class
    270          * @ignore
    271          */
    272         updateDataArray2D: function () {
    273             var i, j, len_u, len_v,
    274                 dataX = [],
    275                 dataY = [],
    276                 c2d;
    277
    278             len_u = this.points.length;
    279             if (len_u !== 0) {
    280                 len_v = this.points[0].length;
    281
    282                 for (i = 0; i < len_u; i++) {
    283                     for (j = 0; j < len_v; j++) {
    284                         c2d = this.view.project3DTo2D(this.points[i][j]);
    285                         dataX.push(c2d[1]);
    286                         dataY.push(c2d[2]);
    287                     }
    288                     dataX.push(NaN);
    289                     dataY.push(NaN);
    290                 }
    291
    292                 for (j = 0; j < len_v; j++) {
    293                     for (i = 0; i < len_u; i++) {
    294                         c2d = this.view.project3DTo2D(this.points[i][j]);
    295                         dataX.push(c2d[1]);
    296                         dataY.push(c2d[2]);
    297                     }
    298                     dataX.push(NaN);
    299                     dataY.push(NaN);
    300                 }
    301             }
    302
    303             return {X: dataX, Y: dataY};
    304         },
    305
    306         addTransform: function (el, transform) {
    307             this.addTransformGeneric(el, transform);
    308             return this;
    309         },
    310
    311         updateTransform: function () {
    312             var t, c, i, j, k,
    313                 len_u, len_v;
    314
    315             if (this.transformations.length === 0 || this.baseElement === null ||
    316                 Type.exists(this._F) // Transformations have only to be applied here
    317                                      // if the curve is defined by arrays
    318             ) {
    319                 return this;
    320             }
    321
    322             t = this.transformations;
    323             for (i = 0; i < t.length; i++) {
    324                 t[i].update();
    325             }
    326             if (this !== this.baseElement) {
    327                 this.points = [];
    328             }
    329
    330             len_u = this.baseElement.points.length;
    331             if (len_u > 0) {
    332                 len_v = this.baseElement.points[0].length;
    333                 for (i = 0; i < len_u; i++) {
    334                     if (this !== this.baseElement) {
    335                         this.points.push([]);
    336                     }
    337                     for (j = 0; j < len_v; j++) {
    338                         if (this === this.baseElement) {
    339                             c = this.points[i][j];
    340                         } else {
    341                             c = this.baseElement.points[i][j];
    342                         }
    343                         for (k = 0; k < t.length; k++) {
    344                             c = Mat.matVecMult(t[k].matrix, c);
    345                         }
    346
    347                         if (this === this.baseElement) {
    348                             this.points[i][j] = c;
    349                         } else {
    350                             this.points[i].push(c);
    351                         }
    352                     }
    353                 }
    354             }
    355
    356             return this;
    357         },
    358
    359         updateDataArray: function() { /* stub */ },
    360
    361         update: function () {
    362             if (this.needsUpdate) {
    363                 this.updateDataArray();
    364                 this.updateCoords();
    365             }
    366             return this;
    367         },
    368
    369         updateRenderer: function () {
    370             this.needsUpdate = false;
    371             return this;
    372         },
    373
    374         projectCoords: function (p, params) {
    375             return Geometry.projectCoordsToParametric(p, this, 2, params);
    376         }
    377
    378         // projectScreenCoords: function (pScr, params) {
    379         //     this.initParamsIfNeeded(params);
    380         //     return Geometry.projectScreenCoordsToParametric(pScr, this, params);
    381         // }
    382     }
    383 );
    384
    385 /**
    386  * @class A 3D parametric surface visualizes a map (u, v) → [X(u, v), Y(u, v), Z(u, v)].
    387  * @pseudo
    388  * @description A 3D parametric surface is defined by a function
    389  *    <i>F: R<sup>2</sup> → R<sup>3</sup></i>.
    390  *
    391  * @name ParametricSurface3D
    392  * @augments Curve
    393  * @constructor
    394  * @type Object
    395  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    396  *
    397  * @param {Function_Function_Function_Array,Function_Array,Function} F<sub>X</sub>,F<sub>Y</sub>,F<sub>Z</sub>,rangeU,rangeV F<sub>X</sub>(u,v), F<sub>Y</sub>(u,v), F<sub>Z</sub>(u,v)
    398  * are functions returning a number, rangeU is the array containing lower and upper bound for the range of parameter u, rangeV is the array containing lower and
    399  * upper bound for the range of parameter v. rangeU and rangeV may also be functions returning an array of length two.
    400  * @param {Function_Array,Function_Array,Function} F,rangeU,rangeV Alternatively: F<sub>[X,Y,Z]</sub>(u,v)
    401  * a function returning an array [x,y,z] of numbers, rangeU and rangeV as above.
    402  *
    403  * @example
    404  * var view = board.create('view3d',
    405  * 		        [[-6, -3], [8, 8],
    406  * 		        [[-5, 5], [-5, 5], [-5, 5]]]);
    407  *
    408  * // Sphere
    409  * var c = view.create('parametricsurface3d', [
    410  *     (u, v) => 2 * Math.sin(u) * Math.cos(v),
    411  *     (u, v) => 2 * Math.sin(u) * Math.sin(v),
    412  *     (u, v) => 2 * Math.cos(u),
    413  *     [0, 2 * Math.PI],
    414  *     [0, Math.PI]
    415  * ], {
    416  *     strokeColor: '#ff0000',
    417  *     stepsU: 30,
    418  *     stepsV: 30
    419  * });
    420  *
    421  * </pre><div id="JXG52da0ecc-1ba9-4d41-850c-36e5120025a5" class="jxgbox" style="width: 500px; height: 500px;"></div>
    422  * <script type="text/javascript">
    423  *     (function() {
    424  *         var board = JXG.JSXGraph.initBoard('JXG52da0ecc-1ba9-4d41-850c-36e5120025a5',
    425  *             {boundingbox: [-8, 8, 8,-8], axis: false, pan: {enabled: false}, showcopyright: false, shownavigation: false});
    426  *     var view = board.create('view3d',
    427  *            [[-6, -3], [8, 8],
    428  *            [[-5, 5], [-5, 5], [-5, 5]]]);
    429  *
    430  *     // Sphere
    431  *     var c = view.create('parametricsurface3d', [
    432  *         (u, v) => 2 * Math.sin(u) * Math.cos(v),
    433  *         (u, v) => 2 * Math.sin(u) * Math.sin(v),
    434  *         (u, v) => 2 * Math.cos(u),
    435  *         [0, 2 * Math.PI],
    436  *         [0, Math.PI]
    437  *     ], {
    438  *         strokeColor: '#ff0000',
    439  *         stepsU: 20,
    440  *         stepsV: 20
    441  *     });
    442  *     })();
    443  *
    444  * </script><pre>
    445  *
    446  */
    447 JXG.createParametricSurface3D = function (board, parents, attributes) {
    448     var view = parents[0],
    449         F, X, Y, Z,
    450         range_u, range_v, attr,
    451         base = null,
    452         transform = null,
    453         el;
    454
    455     if (parents.length === 3) {
    456         base = parents[1];
    457         transform = parents[2];
    458         F = null;
    459         X = null;
    460         Y = null;
    461         Z = null;
    462
    463     } else if (parents.length === 4) {
    464         // [view, F, range_u, range_v]
    465         F = parents[1];
    466         range_u = parents[2];
    467         range_v = parents[3];
    468         X = null;
    469         Y = null;
    470         Z = null;
    471     } else {
    472         // [view, X, Y, Z, range_u, range_v]
    473         X = parents[1];
    474         Y = parents[2];
    475         Z = parents[3];
    476         range_u = parents[4];
    477         range_v = parents[5];
    478         F = null;
    479     }
    480
    481     attr = Type.copyAttributes(attributes, board.options, "surface3d");
    482     el = new JXG.Surface3D(view, F, X, Y, Z, range_u, range_v, attr);
    483
    484     attr = el.setAttr2D(attr);
    485     el.element2D = view.create("curve", [[], []], attr);
    486     el.element2D.view = view;
    487     if (base !== null) {
    488         el.addTransform(base, transform);
    489         el.addParents(base);
    490     }
    491
    492     /**
    493      * @class
    494      * @ignore
    495      */
    496     el.element2D.updateDataArray = function () {
    497         var ret = el.updateDataArray2D();
    498         this.dataX = ret.X;
    499         this.dataY = ret.Y;
    500     };
    501     el.addChild(el.element2D);
    502     el.inherits.push(el.element2D);
    503     el.element2D.setParents(el);
    504
    505     el.element2D.prepareUpdate().update();
    506     if (!board.isSuspendedUpdate) {
    507         el.element2D.updateVisibility().updateRenderer();
    508     }
    509
    510     return el;
    511 };
    512 JXG.registerElement("parametricsurface3d", JXG.createParametricSurface3D);
    513
    514 /**
    515  * @class A 3D functiongraph  visualizes a map (x, y) → f(x, y).
    516  * The graph is a {@link Curve3D} element.
    517  * @pseudo
    518  * @description A 3D function graph is defined by a function
    519  *    <i>F: R<sup>2</sup> → R</i>.
    520  *
    521  * @name Functiongraph3D
    522  * @augments ParametricSurface3D
    523  * @constructor
    524  * @type Object
    525  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    526  * @param {Function,String_Array_Array} F,rangeX,rangeY  F(x,y) is a function returning a number (or a JessieCode string), rangeX is the array containing
    527  * lower and upper bound for the range of x, rangeY is the array containing
    528  * lower and upper bound for the range of y.
    529  * @example
    530  * var box = [-5, 5];
    531  * var view = board.create('view3d',
    532  *     [
    533  *         [-6, -3], [8, 8],
    534  *         [box, box, box]
    535  *     ],
    536  *     {
    537  *         xPlaneRear: {visible: false},
    538  *         yPlaneRear: {visible: false},
    539  *     });
    540  *
    541  * // Function F to be plotted
    542  * var F = (x, y) => Math.sin(x * y / 4);
    543  *
    544  * // 3D surface
    545  * var c = view.create('functiongraph3d', [
    546  *     F,
    547  *     box, // () => [-s.Value()*5, s.Value() * 5],
    548  *     box, // () => [-s.Value()*5, s.Value() * 5],
    549  * ], {
    550  *     strokeWidth: 0.5,
    551  *     stepsU: 70,
    552  *     stepsV: 70
    553  * });
    554  *
    555  * </pre><div id="JXG87646dd4-9fe5-4c21-8734-089abc612515" class="jxgbox" style="width: 500px; height: 500px;"></div>
    556  * <script type="text/javascript">
    557  *     (function() {
    558  *         var board = JXG.JSXGraph.initBoard('JXG87646dd4-9fe5-4c21-8734-089abc612515',
    559  *             {boundingbox: [-8, 8, 8,-8], axis: false, pan: {enabled: false}, showcopyright: false, shownavigation: false});
    560  *     var box = [-5, 5];
    561  *     var view = board.create('view3d',
    562  *         [
    563  *             [-6, -3], [8, 8],
    564  *             [box, box, box]
    565  *         ],
    566  *         {
    567  *             xPlaneRear: {visible: false},
    568  *             yPlaneRear: {visible: false},
    569  *         });
    570  *
    571  *     // Function F to be plotted
    572  *     var F = (x, y) => Math.sin(x * y / 4);
    573  *
    574  *     // 3D surface
    575  *     var c = view.create('functiongraph3d', [
    576  *         F,
    577  *         box, // () => [-s.Value()*5, s.Value() * 5],
    578  *         box, // () => [-s.Value()*5, s.Value() * 5],
    579  *     ], {
    580  *         strokeWidth: 0.5,
    581  *         stepsU: 70,
    582  *         stepsV: 70
    583  *     });
    584  *     })();
    585  *
    586  * </script><pre>
    587  *
    588  */
    589 JXG.createFunctiongraph3D = function (board, parents, attributes) {
    590     var view = parents[0],
    591         X = function (u, v) {
    592             return u;
    593         },
    594         Y = function (u, v) {
    595             return v;
    596         },
    597         Z = Type.createFunction(parents[1], board, 'x, y'),
    598         range_u = parents[2],
    599         range_v = parents[3],
    600         el;
    601
    602     el = view.create("parametricsurface3d", [X, Y, Z, range_u, range_v], attributes);
    603     el.elType = 'functiongraph3d';
    604     return el;
    605 };
    606 JXG.registerElement("functiongraph3d", JXG.createFunctiongraph3D);
    607