      1 /*
      2     Copyright 2008-2024
      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 //, GeometryElement3D) {
     37
     38 /**
     39  * A 3D text is a basic geometric element.
     40  * @class Creates a new 3D point object. Do not use this constructor to create a 3D point. Use {@link JXG.View3D#create} with
     41  * type {@link Point3D} instead.
     42  * @augments JXG.GeometryElement3D
     43  * @augments JXG.GeometryElement
     44  * @param {JXG.View3D} view The 3D view the point is drawn on.
     45  * @param {Function|Array} F Array of numbers, array of functions or function returning an array with defines the user coordinates of the point.
     46  * @param {JXG.GeometryElement3D} slide Object the 3D point should be bound to. If null, the point is a free point.
     47  * @param {Object} attributes An object containing visual properties like in {@link JXG.Options#point3d} and
     48  * {@link JXG.Options#elements}, and optional a name and an id.
     49  * @see JXG.Board#generateName
     50  */
     51 JXG.Text3D = function (view, F, text, slide, attributes) {
     52     this.constructor(view.board, attributes, Const.OBJECT_TYPE_TEXT3D, Const.OBJECT_CLASS_3D);
     53     this.constructor3D(view, "text3d");
     54
     55     this.board.finalizeAdding(this);
     56
     57     // add the new point to its view's point list
     58     if (view.visProp.depthorderpoints) {
     59         view.points.push(this);
     60     }
     61
     62     /**
     63      * Homogeneous coordinates of a Point3D, i.e. array of length 4: [w, x, y, z]. Usually, w=1 for finite points and w=0 for points
     64      * which are infinitely far.
     65      *
     66      * @example
     67      *   p.coords;
     68      *
     69      * @name Point3D#coords
     70      * @type Array
     71      * @private
     72      */
     73     this.coords = [0, 0, 0, 0];
     74
     75     /**
     76      * Function or array of functions or array of numbers defining the coordinates of the point, used in {@link updateCoords}.
     77      *
     78      * @name Point3D#F
     79      * @function
     80      * @private
     81      *
     82      * @see updateCoords
     83      */
     84     this.F = F;
     85
     86     /**
     87      * Optional slide element, i.e. element the Point3D lives on.
     88      *
     89      * @example
     90      *   p.slide;
     91      *
     92      * @name Point3D#slide
     93      * @type JXG.GeometryElement3D
     94      * @default null
     95      * @private
     96      *
     97      */
     98     this.slide = slide;
     99
    100     /**
    101      * Get x-coordinate of a 3D point.
    102      *
    103      * @name X
    104      * @memberOf Point3D
    105      * @function
    106      * @returns {Number}
    107      *
    108      * @example
    109      *   p.X();
    110      */
    111     this.X = function () {
    112         return this.coords[1];
    113     };
    114
    115     /**
    116      * Get y-coordinate of a 3D point.
    117      *
    118      * @name Y
    119      * @memberOf Point3D
    120      * @function
    121      * @returns Number
    122      *
    123      * @example
    124      *   p.Y();
    125      */
    126     this.Y = function () {
    127         return this.coords[2];
    128     };
    129
    130     /**
    131      * Get z-coordinate of a 3D point.
    132      *
    133      * @name Z
    134      * @memberOf Point3D
    135      * @function
    136      * @returns Number
    137      *
    138      * @example
    139      *   p.Z();
    140      */
    141     this.Z = function () {
    142         return this.coords[3];
    143     };
    144
    145     /**
    146      * Store the last position of the 2D point for the optimizer.
    147      *
    148      * @type Array
    149      * @private
    150      */
    151     this._params = [];
    152
    153     this._c2d = null;
    154
    155     this.methodMap = Type.deepCopy(this.methodMap, {
    156         // TODO
    157     });
    158 };
    159 JXG.Text3D.prototype = new JXG.GeometryElement();
    160 Type.copyPrototypeMethods(JXG.Text3D, JXG.GeometryElement3D, "constructor3D");
    161
    162 JXG.extend(
    163     JXG.Text3D.prototype,
    164     /** @lends JXG.Text3D.prototype */ {
    165         /**
    166          * Update the homogeneous coords array.
    167          *
    168          * @name updateCoords
    169          * @memberOf Text3D
    170          * @function
    171          * @returns {Object} Reference to the Text3D object
    172          * @private
    173          * @example
    174          *    p.updateCoords();
    175          */
    176         updateCoords: function () {
    177             var i;
    178
    179             if (Type.isFunction(this.F)) {
    180                 // this.coords = [1].concat(Type.evaluate(this.F));
    181                 this.coords = Type.evaluate(this.F);
    182                 this.coords.unshift(1);
    183             } else {
    184                 this.coords[0] = 1;
    185                 for (i = 0; i < 3; i++) {
    186                     // Attention: if F is array of numbers, coords are not updated.
    187                     // Otherwise, dragging will not work anymore.
    188                     if (Type.isFunction(this.F[i])) {
    189                         this.coords[i + 1] = Type.evaluate(this.F[i]);
    190                     }
    191                 }
    192             }
    193             return this;
    194         },
    195
    196         /**
    197          * Initialize the coords array.
    198          *
    199          * @private
    200          * @returns {Object} Reference to the Text3D object
    201          */
    202         initCoords: function () {
    203             var i;
    204
    205             if (Type.isFunction(this.F)) {
    206                 // this.coords = [1].concat(Type.evaluate(this.F));
    207                 this.coords = Type.evaluate(this.F);
    208                 this.coords.unshift(1);
    209             } else {
    210                 this.coords[0] = 1;
    211                 for (i = 0; i < 3; i++) {
    212                     this.coords[i + 1] = Type.evaluate(this.F[i]);
    213                 }
    214             }
    215             return this;
    216         },
    217
    218         /**
    219          * Normalize homogeneous coordinates such the the first coordinate (the w-coordinate is equal to 1 or 0)-
    220          *
    221          * @name normalizeCoords
    222          * @memberOf Text3D
    223          * @function
    224          * @returns {Object} Reference to the Text3D object
    225          * @private
    226          * @example
    227          *    p.normalizeCoords();
    228          */
    229         normalizeCoords: function () {
    230             if (Math.abs(this.coords[0]) > Mat.eps) {
    231                 this.coords[1] /= this.coords[0];
    232                 this.coords[2] /= this.coords[0];
    233                 this.coords[3] /= this.coords[0];
    234                 this.coords[0] = 1.0;
    235             }
    236             return this;
    237         },
    238
    239         /**
    240          * Set the position of a 3D point.
    241          *
    242          * @name setPosition
    243          * @memberOf Text3D
    244          * @function
    245          * @param {Array} coords 3D coordinates. Either of the form [x,y,z] (Euclidean) or [w,x,y,z] (homogeneous).
    246          * @param {Boolean} [noevent] If true, no events are triggered.
    247          * @returns {Object} Reference to the Text3D object
    248          *
    249          * @example
    250          *    p.setPosition([1, 3, 4]);
    251          */
    252         setPosition: function (coords, noevent) {
    253             var c = this.coords;
    254                 // oc = this.coords.slice(); // Copy of original values
    255
    256             if (coords.length === 3) {
    257                 // Euclidean coordinates
    258                 c[0] = 1.0;
    259                 c[1] = coords[0];
    260                 c[2] = coords[1];
    261                 c[3] = coords[2];
    262             } else {
    263                 // Homogeneous coordinates (normalized)
    264                 c[0] = coords[0];
    265                 c[1] = coords[1];
    266                 c[2] = coords[2];
    267                 c[3] = coords[2];
    268                 this.normalizeCoords();
    269             }
    270
    271             // console.log(el.emitter, !noevent, oc[0] !== c[0] || oc[1] !== c[1] || oc[2] !== c[2] || oc[3] !== c[3]);
    272             // Not yet working TODO
    273             // if (el.emitter && !noevent &&
    274             //     (oc[0] !== c[0] || oc[1] !== c[1] || oc[2] !== c[2] || oc[3] !== c[3])) {
    275             //     this.triggerEventHandlers(['update3D'], [oc]);
    276             // }
    277             return this;
    278         },
    279
    280         update: function (drag) {
    281             var c3d, foot;
    282
    283             // Update is called from board.updateElements
    284             if (
    285                 this.element2D.draggable() &&
    286                 Geometry.distance(this._c2d, this.element2D.coords.usrCoords) !== 0
    287             ) {
    288                 if (this.slide) {
    289                     this.coords = this.slide.projectScreenCoords(
    290                         [this.element2D.X(), this.element2D.Y()],
    291                         this._params
    292                     );
    293                     this.element2D.coords.setCoordinates(
    294                         Const.COORDS_BY_USER,
    295                         this.view.project3DTo2D(this.coords)
    296                     );
    297                 } else {
    298                     if (this.view.isVerticalDrag()) {
    299                         // Drag the point in its vertical to the xy plane
    300                         c3d = this.view.project2DTo3DVertical(this.element2D, this.coords);
    301                     } else {
    302                         // Drag the point in its xy plane
    303                         foot = [1, 0, 0, this.coords[3]];
    304                         c3d = this.view.project2DTo3DPlane(this.element2D, [1, 0, 0, 1], foot);
    305                     }
    306                     if (c3d[0] !== 0) {
    307                         this.coords = this.view.project3DToCube(c3d);
    308                     }
    309                 }
    310             } else {
    311                 this.updateCoords();
    312                 if (this.slide) {
    313                     this.coords = this.slide.projectCoords(
    314                         [this.X(), this.Y(), this.Z()],
    315                         this._params
    316                     );
    317                 }
    318                 // Update 2D point from its 3D view
    319                 this.element2D.coords.setCoordinates(
    320                     Const.COORDS_BY_USER,
    321                     this.view.project3DTo2D([1, this.X(), this.Y(), this.Z()].slice(1))
    322                 );
    323                 this.element2D.prepareUpdate().update();
    324             }
    325             this._c2d = this.element2D.coords.usrCoords.slice();
    326
    327             return this;
    328         },
    329
    330         updateRenderer: function () {
    331             this.needsUpdate = false;
    332             return this;
    333         },
    334
    335         /**
    336          * Check whether a point's homogeneous coordinate vector is zero.
    337          * @returns {Boolean} True if the coordinate vector is zero; false otherwise.
    338          */
    339         isIllDefined: function () {
    340             return Type.cmpArrays(this.coords, [0, 0, 0, 0]);
    341         },
    342
    343         // Not yet working
    344         __evt__update3D: function (oc) {}
    345     }
    346 );
    347
    348 /**
    349  * @class This element is used to provide a constructor for a 3D Text.
    350  * @pseudo
    351  * @description A Text3D object is defined by 3 coordinates [x, y, z, text] or an array / function for the position of the text
    352  * and a string or function defining the text.
    353  * <p>
    354  * That is, all numbers can also be provided as functions returning a number.
    355  * <p>
    356  * At the time being, text display is independent from the camera view.
    357  *
    358  * @name Text3D
    359  * @augments JXG.Text3D
    360  * @augments Text
    361  * @constructor
    362  * @throws {Exception} If the element cannot be constructed with the given parent
    363  * objects an exception is thrown.
    364  * @param {number,function_number,function_number,function_String,function} x,y,z,txt The coordinates are given as x, y, z consisting of numbers of functions and the text.
    365  * @param {array,function_string} F,txt Alternatively, the coordinates can be supplied as array or function returning an array.
    366  *
    367  * @example
    368  *     var bound = [-4, 6];
    369  *     var view = board.create('view3d',
    370  *         [[-4, -3], [8, 8],
    371  *         [bound, bound, bound]],
    372  *         {
    373  *             projection: 'central'
    374  *         });
    375  *
    376  *     var txt1 = view.create('text3d', [[1, 2, 1], 'hello'], {
    377  *         fontSize: 20,
    378  *     });
    379  *
    380  * </pre><div id="JXGb61d7c50-617a-4bed-9a45-13c949f90e94" class="jxgbox" style="width: 300px; height: 300px;"></div>
    381  * <script type="text/javascript">
    382  *     (function() {
    383  *         var board = JXG.JSXGraph.initBoard('JXGb61d7c50-617a-4bed-9a45-13c949f90e94',
    384  *             {boundingbox: [-8, 8, 8,-8], axis: false, pan: {enabled: false}, showcopyright: false, shownavigation: false});
    385  *         var bound = [-4, 6];
    386  *         var view = board.create('view3d',
    387  *             [[-4, -3], [8, 8],
    388  *             [bound, bound, bound]],
    389  *             {
    390  *                 projection: 'central'
    391  *             });
    392  *
    393  *         var txt1 = view.create('text3d', [[1, 2, 1], 'hello'], {
    394  *             fontSize: 20,
    395  *         });
    396  *
    397  *     })();
    398  *
    399  * </script><pre>
    400  *
    401  */
    402 JXG.createText3D = function (board, parents, attributes) {
    403     var view = parents[0],
    404         attr, F, slide,
    405         text,
    406         c2d, el;
    407
    408     // If the last element of parents is a 3D object,
    409     // the point is a glider on that element.
    410     if (parents.length > 3 && Type.exists(parents[parents.length - 1].is3D)) {
    411         slide = parents.pop();
    412     } else {
    413         slide = null;
    414     }
    415
    416     if (parents.length === 3) {
    417         // [view, array|fun, text] (Array [x, y, z] | function) returning [x, y, z] and string | function
    418         F = parents[1];
    419         text = parents[2];
    420     } else if (parents.length === 5) {
    421         // [view, x, y, z, text], (3 numbers | functions) sand string | function
    422         F = parents.slice(1, 4);
    423         text = parents[4];
    424     } else {
    425         throw new Error(
    426             "JSXGraph: Can't create text3d with parent types '" +
    427                 typeof parents[0] +
    428                 "' and '" +
    429                 typeof parents[1] +
    430                 "'." +
    431                 "\nPossible parent types: [[x,y,z], text], [x,y,z, text]"
    432         );
    433         //  "\nPossible parent types: [[x,y,z]], [x,y,z], [element,transformation]"); // TODO
    434     }
    435
    436     attr = Type.copyAttributes(attributes, board.options, 'text3d');
    437     el = new JXG.Text3D(view, F, text, slide, attr);
    438     el.initCoords();
    439
    440     c2d = view.project3DTo2D(el.coords);
    441
    442     attr = el.setAttr2D(attr);
    443     el.element2D = view.create('text', [c2d[1], c2d[2], text], attr);
    444
    445     el.element2D.view = view;
    446     el.addChild(el.element2D);
    447     el.inherits.push(el.element2D);
    448     el.element2D.setParents(el);
    449
    450
    451     // if this point is a glider, record that in the update tree
    452     if (el.slide) {
    453         el.slide.addChild(el);
    454         el.setParents(el.slide);
    455     }
    456
    457     el._c2d = el.element2D.coords.usrCoords.slice(); // Store a copy of the coordinates to detect dragging
    458
    459     return el;
    460 };
    461
    462 JXG.registerElement("text3d", JXG.createText3D);
    463