      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
     36 /**
     37  * Constructor for 3D curves.
     38  * @class Creates a new 3D curve object. Do not use this constructor to create a 3D curve. Use {@link JXG.View3D#create} with type {@link Curve3D} instead.
     39  *
     40  * @augments JXG.GeometryElement3D
     41  * @augments JXG.GeometryElement
     42  * @param {View3D} view
     43  * @param {Function} F
     44  * @param {Function} X
     45  * @param {Function} Y
     46  * @param {Function} Z
     47  * @param {Array} range
     48  * @param {Object} attributes
     49  * @see JXG.Board#generateName
     50  */
     51 JXG.Curve3D = function (view, F, X, Y, Z, range, attributes) {
     52     this.constructor(view.board, attributes, Const.OBJECT_TYPE_CURVE3D, Const.OBJECT_CLASS_3D);
     53     this.constructor3D(view, "curve3d");
     54
     55     this.board.finalizeAdding(this);
     56
     57     /**
     58      * @function
     59      * @ignore
     60      */
     61     this.F = F;
     62
     63     /**
     64      * Function which maps u to x; i.e. it defines the x-coordinate of the curve
     65      * @function
     66      * @returns Number
     67      */
     68     this.X = X;
     69
     70     /**
     71      * Function which maps u to y; i.e. it defines the y-coordinate of the curve
     72      * @function
     73      * @returns Number
     74      */
     75     this.Y = Y;
     76
     77     /**
     78      * Function which maps u to z; i.e. it defines the x-coordinate of the curve
     79      * @function
     80      * @returns Number
     81      */
     82     this.Z = Z;
     83
     84     this.dataX = null;
     85     this.dataY = null;
     86     this.dataZ = null;
     87
     88     if (this.F !== null) {
     89         this.X = function (u) {
     90             return this.F(u)[0];
     91         };
     92         this.Y = function (u) {
     93             return this.F(u)[1];
     94         };
     95         this.Z = function (u) {
     96             return this.F(u)[2];
     97         };
     98     }
     99
    100     this.range = range;
    101
    102     this.methodMap = Type.deepCopy(this.methodMap, {
    103         // TODO
    104     });
    105 };
    106 JXG.Curve3D.prototype = new JXG.GeometryElement();
    107 Type.copyPrototypeMethods(JXG.Curve3D, JXG.GeometryElement3D, "constructor3D");
    108
    109 JXG.extend(
    110     JXG.Curve3D.prototype,
    111     /** @lends JXG.Curve3D.prototype */ {
    112         updateDataArray2D: function () {
    113             var steps = Type.evaluate(this.visProp.numberpointshigh),
    114                 r, s, e, delta, c2d, u, dataX, dataY,
    115                 i,
    116                 p = [0, 0, 0];
    117
    118             dataX = [];
    119             dataY = [];
    120             if (Type.exists(this.dataX)) {
    121                 steps = this.dataX.length;
    122                 for (u = 0; u < steps; u++) {
    123                     p = [this.dataX[u], this.dataY[u], this.dataZ[u]];
    124                     c2d = this.view.project3DTo2D(p);
    125                     dataX.push(c2d[1]);
    126                     dataY.push(c2d[2]);
    127                 }
    128             } else if (Type.isArray(this.X)) {
    129                 steps = this.X.length;
    130                 for (u = 0; u < steps; u++) {
    131                     p = [this.X[u], this.Y[u], this.Z[u]];
    132                     c2d = this.view.project3DTo2D(p);
    133                     dataX.push(c2d[1]);
    134                     dataY.push(c2d[2]);
    135                 }
    136             } else {
    137                 r = Type.evaluate(this.range);
    138                 s = Type.evaluate(r[0]);
    139                 e = Type.evaluate(r[1]);
    140                 delta = (e - s) / (steps - 1);
    141                 for (i = 0, u = s; i < steps && u <= e; i++, u += delta) {
    142                     if (this.F !== null) {
    143                         p = this.F(u);
    144                     } else {
    145                         p = [this.X(u), this.Y(u), this.Z(u)];
    146                     }
    147                     c2d = this.view.project3DTo2D(p);
    148                     dataX.push(c2d[1]);
    149                     dataY.push(c2d[2]);
    150                 }
    151             }
    152             return { X: dataX, Y: dataY };
    153         },
    154
    155         updateDataArray: function() {
    156         },
    157
    158         update: function () {
    159             // if (this.needsUpdate) {
    160                 this.updateDataArray();
    161             // }
    162             return this;
    163         },
    164
    165         updateRenderer: function () {
    166             this.needsUpdate = false;
    167             return this;
    168         }
    169     }
    170 );
    171
    172 /**
    173  * @class This element creates a 3D parametric curves.
    174  * @pseudo
    175  * @description A 3D parametric curve is defined by a function
    176  *    <i>F: R<sup>1</sup> → R<sup>3</sup></i>.
    177  *
    178  * @name Curve3D
    179  * @augments Curve
    180  * @constructor
    181  * @type Object
    182  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    183  * @param {Function_Function_Function_Array,Function} F<sub>X</sub>,F<sub>Y</sub>,F<sub>Z</sub>,range
    184  * F<sub>X</sub>(u), F<sub>Y</sub>(u), F<sub>Z</sub>(u) are functions returning a number, range is the array containing
    185  * lower and upper bound for the range of the parameter u. range may also be a function returning an array of length two.
    186  * @param {Function_Array,Function} F,range Alternatively: F<sub>[X,Y,Z]</sub>(u) a function returning an array [x,y,z] of
    187  * numbers, range as above.
    188  * @param {Array_Array_Array} X,Y,Z Three arrays containing the coordinate points which define the curve.
    189  */
    190 JXG.createCurve3D = function (board, parents, attributes) {
    191     var view = parents[0],
    192         F, X, Y, Z, range, attr, el;
    193
    194     if (parents.length === 3) {
    195         F = parents[1];
    196         range = parents[2];
    197         X = null;
    198         Y = null;
    199         Z = null;
    200     } else {
    201         X = parents[1];
    202         Y = parents[2];
    203         Z = parents[3];
    204         range = parents[4];
    205         F = null;
    206     }
    207     // TODO Throw error
    208
    209     attr = Type.copyAttributes(attributes, board.options, "curve3d");
    210     el = new JXG.Curve3D(view, F, X, Y, Z, range, attr);
    211
    212     attr = el.setAttr2D(attr);
    213     el.element2D = view.create("curve", [[], []], attr);
    214     /**
    215      * @class
    216      * @ignore
    217      */
    218     el.element2D.updateDataArray = function () {
    219         var ret = el.updateDataArray2D();
    220         this.dataX = ret.X;
    221         this.dataY = ret.Y;
    222     };
    223     el.addChild(el.element2D);
    224     el.inherits.push(el.element2D);
    225     el.element2D.setParents(el);
    226
    227     el.element2D.prepareUpdate().update();
    228     if (!board.isSuspendedUpdate) {
    229         el.element2D.updateVisibility().updateRenderer();
    230     }
    231
    232     return el;
    233 };
    234
    235 JXG.registerElement("curve3d", JXG.createCurve3D);
    236
    237 /**
    238  * @class 3D vector field.
    239  * <p>
    240  * Plot a vector field either given by three functions
    241  * f1(x, y, z), f2(x, y, z), and f3(x, y, z) or by a function f(x, y, z)
    242  * returning an array of size 3.
    243  *
    244  * @pseudo
    245  * @name Vectorfield3D
    246  * @augments JXG.Curve3D
    247  * @constructor
    248  * @type JXG.Curve3D
    249  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
    250  * Parameter options:
    251  * @param {Array|Function|String} F Either an array containing three functions f1(x, y, z), f2(x, y, z),
    252  * and f3(x, y) or function f(x, y, z) returning an array of length 3.
    253  * @param {Array} xData Array of length 3 containing start value for x, number of steps,
    254  * end value of x. The vector field will contain (number of steps) + 1 vectors in direction of x.
    255  * @param {Array} yData Array of length 3 containing start value for y, number of steps,
    256  * end value of y. The vector field will contain (number of steps) + 1 vectors in direction of y.
    257  * @param {Array} zData Array of length 3 containing start value for z, number of steps,
    258  * end value of z. The vector field will contain (number of steps) + 1 vectors in direction of z.
    259  */
    260 JXG.createVectorfield3D = function (board, parents, attributes) {
    261     var view = parents[0],
    262         el, attr;
    263
    264     if (!(parents.length >= 5 &&
    265         (Type.isArray(parents[1]) || Type.isFunction(parents[0]) || Type.isString(parents[0])) &&
    266         (Type.isArray(parents[2]) && parents[1].length === 3) &&
    267         (Type.isArray(parents[3]) && parents[2].length === 3) &&
    268         (Type.isArray(parents[4]) && parents[3].length === 3)
    269     )) {
    270         throw new Error(
    271             "JSXGraph: Can't create vector field 3D with parent types " +
    272             "'" + typeof parents[0] + "', " +
    273             "'" + typeof parents[1] + "', " +
    274             "'" + typeof parents[2] + "'."  +
    275             "'" + typeof parents[1] + "', "
    276         );
    277     }
    278
    279     attr = Type.copyAttributes(attributes, board.options, 'vectorfield3d');
    280     el = view.create('curve3d', [[], [], []], attr);
    281
    282     /**
    283      * Set the defining functions of 3D vector field.
    284      * @memberOf Vectorfield3D
    285      * @name setF
    286      * @function
    287      * @param {Array|Function} func Either an array containing three functions f1(x, y, z),
    288      * f2(x, y, z), and f3(x, y, z) or function f(x, y, z) returning an array of length 3.
    289      * @returns {Object} Reference to the 3D vector field object.
    290      *
    291      * @example
    292      * field.setF([(x, y, z) => Math.sin(y), (x, y, z) => Math.cos(x), (x, y, z) => z]);
    293      * board.update();
    294      *
    295      */
    296     el.setF = function (func, varnames) {
    297         var f0, f1, f2;
    298         if (Type.isArray(func)) {
    299             f0 = Type.createFunction(func[0], this.board, varnames);
    300             f1 = Type.createFunction(func[1], this.board, varnames);
    301             f2 = Type.createFunction(func[2], this.board, varnames);
    302             /**
    303              * @ignore
    304              */
    305             this.F = function (x, y, z) {
    306                 return [f0(x, y, z), f1(x, y, z), f2(x, y, z)];
    307             };
    308         } else {
    309             this.F = Type.createFunction(func, el.board, varnames);
    310         }
    311         return this;
    312     };
    313
    314     el.setF(parents[1], 'x, y, z');
    315     el.xData = parents[2];
    316     el.yData = parents[3];
    317     el.zData = parents[4];
    318
    319     el.updateDataArray = function () {
    320         var k, i, j,
    321             v, nrm,
    322             x, y, z,
    323             scale = Type.evaluate(this.visProp.scale),
    324             start = [
    325                 Type.evaluate(this.xData[0]),
    326                 Type.evaluate(this.yData[0]),
    327                 Type.evaluate(this.zData[0])
    328             ],
    329             steps = [
    330                 Type.evaluate(this.xData[1]),
    331                 Type.evaluate(this.yData[1]),
    332                 Type.evaluate(this.zData[1])
    333             ],
    334             end = [
    335                 Type.evaluate(this.xData[2]),
    336                 Type.evaluate(this.yData[2]),
    337                 Type.evaluate(this.zData[2])
    338             ],
    339             delta = [
    340                 (end[0] - start[0]) / steps[0],
    341                 (end[1] - start[1]) / steps[1],
    342                 (end[2] - start[2]) / steps[2]
    343             ],
    344             phi, theta1, theta2, theta,
    345             showArrow = Type.evaluate(this.visProp.arrowhead.enabled),
    346             leg, leg_x, leg_y, leg_z, alpha;
    347
    348         if (showArrow) {
    349             // Arrow head style
    350             // leg = 8;
    351             // alpha = Math.PI * 0.125;
    352             leg = Type.evaluate(this.visProp.arrowhead.size);
    353             alpha = Type.evaluate(this.visProp.arrowhead.angle);
    354             leg_x = leg / board.unitX;
    355             leg_y = leg / board.unitY;
    356             leg_z = leg / Math.sqrt(board.unitX * board.unitY);
    357         }
    358
    359         this.dataX = [];
    360         this.dataY = [];
    361         this.dataZ = [];
    362         for (i = 0, x = start[0]; i <= steps[0]; x += delta[0], i++) {
    363             for (j = 0, y = start[1]; j <= steps[1]; y += delta[1], j++) {
    364                 for (k = 0, z = start[2]; k <= steps[2]; z += delta[2], k++) {
    365                     v = this.F(x, y, z);
    366                     nrm = Mat.norm(v);
    367                     if (nrm < Number.EPSILON) {
    368                         continue;
    369                     }
    370
    371                     v[0] *= scale;
    372                     v[1] *= scale;
    373                     v[2] *= scale;
    374                     this.dataX = this.dataX.concat([x, x + v[0], NaN]);
    375                     this.dataY = this.dataY.concat([y, y + v[1], NaN]);
    376                     this.dataZ = this.dataZ.concat([z, z + v[2], NaN]);
    377
    378                     if (showArrow) {
    379                         // Arrow head
    380                         nrm *= scale;
    381                         phi = Math.atan2(v[1], v[0]);
    382                         theta = Math.asin(v[2] / nrm);
    383                         theta1 = theta - alpha;
    384                         theta2 = theta + alpha;
    385                         this.dataX = this.dataX.concat([
    386                             x + v[0] - leg_x * Math.cos(phi) * Math.cos(theta1),
    387                             x + v[0],
    388                             x + v[0] - leg_x * Math.cos(phi) * Math.cos(theta2),
    389                             NaN]);
    390                         this.dataY = this.dataY.concat([
    391                             y + v[1] - leg_y * Math.sin(phi) * Math.cos(theta1),
    392                             y + v[1],
    393                             y + v[1] - leg_y * Math.sin(phi) * Math.cos(theta2),
    394                             NaN]);
    395                         this.dataZ = this.dataZ.concat([
    396                             z + v[2] - leg_z * Math.sin(theta2),
    397                             z + v[2],
    398                             z + v[2] - leg_z * Math.sin(theta1),
    399                             NaN]);
    400                     }
    401                 }
    402             }
    403         }
    404     };
    405
    406     el.methodMap = Type.deepCopy(el.methodMap, {
    407         setF: "setF"
    408     });
    409
    410     return el;
    411 };
    412
    413 JXG.registerElement("vectorfield3D", JXG.createVectorfield3D);
    414