      1 /*
      2     Copyright 2008-2025
      3         Matthias Ehmann,
      4         Michael Gerhaeuser,
      5         Carsten Miller,
      6         Bianca Valentin,
      7         Alfred Wassermann,
      8         Peter Wilfahrt
      9
     10     This file is part of JSXGraph.
     11
     12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
     13
     14     You can redistribute it and/or modify it under the terms of the
     15
     16       * GNU Lesser General Public License as published by
     17         the Free Software Foundation, either version 3 of the License, or
     18         (at your option) any later version
     19       OR
     20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
     21
     22     JSXGraph is distributed in the hope that it will be useful,
     23     but WITHOUT ANY WARRANTY; without even the implied warranty of
     24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     25     GNU Lesser General Public License for more details.
     26
     27     You should have received a copy of the GNU Lesser General Public License and
     28     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
     29     and <https://opensource.org/licenses/MIT/>.
     30  */
     31
     32 /*global JXG: true, define: true*/
     33 /*jslint nomen: true, plusplus: true*/
     34
     35 /**
     36  * @fileoverview This file contains code for transformations of geometrical objects.
     37  */
     38
     39 import JXG from "../jxg.js";
     40 import Const from "./constants.js";
     41 import Mat from "../math/math.js";
     42 import Type from "../utils/type.js";
     43
     44 /**
     45  * A transformation consists of a 3x3 matrix, i.e. it is a projective transformation.
     46  * @class Creates a new transformation object. Do not use this constructor to create a transformation.
     47  * Use {@link JXG.Board#create} with
     48  * type {@link Transformation} instead.
     49  * @constructor
     50  * @param {JXG.Board} board The board the transformation is part of.
     51  * @param {String} type Can be
     52  * <ul><li> 'translate'
     53  * <li> 'scale'
     54  * <li> 'reflect'
     55  * <li> 'rotate'
     56  * <li> 'shear'
     57  * <li> 'generic'
     58  * <li> 'matrix'
     59  * </ul>
     60  * @param {Object} params The parameters depend on the transformation type
     61  *
     62  * <p>
     63  * Translation matrix:
     64  * <pre>
     65  * ( 1  0  0)   ( z )
     66  * ( a  1  0) * ( x )
     67  * ( b  0  1)   ( y )
     68  * </pre>
     69  *
     70  * <p>
     71  * Scale matrix:
     72  * <pre>
     73  * ( 1  0  0)   ( z )
     74  * ( 0  a  0) * ( x )
     75  * ( 0  0  b)   ( y )
     76  * </pre>
     77  *
     78  * <p>
     79  * A rotation matrix with angle a (in Radians)
     80  * <pre>
     81  * ( 1    0        0      )   ( z )
     82  * ( 0    cos(a)   -sin(a)) * ( x )
     83  * ( 0    sin(a)   cos(a) )   ( y )
     84  * </pre>
     85  *
     86  * <p>
     87  * Shear matrix:
     88  * <pre>
     89  * ( 1  0  0)   ( z )
     90  * ( 0  1  a) * ( x )
     91  * ( 0  b  1)   ( y )
     92  * </pre>
     93  *
     94  * <p>Generic transformation:
     95  * <pre>
     96  * ( a  b  c )   ( z )
     97  * ( d  e  f ) * ( x )
     98  * ( g  h  i )   ( y )
     99  * </pre>
    100  *
    101  */
    102 JXG.Transformation = function (board, type, params, is3D) {
    103     this.elementClass = Const.OBJECT_CLASS_OTHER;
    104     this.type = Const.OBJECT_TYPE_TRANSFORMATION;
    105
    106     if (is3D) {
    107         this.is3D = true;
    108         this.matrix = [
    109             [1, 0, 0, 0],
    110             [0, 1, 0, 0],
    111             [0, 0, 1, 0],
    112             [0, 0, 0, 1]
    113         ];
    114     } else {
    115         this.is3D = false;
    116         this.matrix = [
    117             [1, 0, 0],
    118             [0, 1, 0],
    119             [0, 0, 1]
    120         ];
    121     }
    122
    123     this.board = board;
    124     this.isNumericMatrix = false;
    125     if (this.is3D) {
    126         this.setMatrix3D(params[0] /* view3d */, type, params.slice(1));
    127     } else {
    128         this.setMatrix(board, type, params);
    129     }
    130
    131     this.methodMap = {
    132         apply: "apply",
    133         applyOnce: "applyOnce",
    134         bindTo: "bindTo",
    135         bind: "bindTo",
    136         melt: "melt",
    137         meltTo: "meltTo"
    138     };
    139 };
    140
    141 JXG.Transformation.prototype = {};
    142
    143 JXG.extend(
    144     JXG.Transformation.prototype,
    145     /** @lends JXG.Transformation.prototype */ {
    146         /**
    147          * Updates the numerical data for the transformation, i.e. the entry of the subobject matrix.
    148          * @returns {JXG.Transform} returns pointer to itself
    149          */
    150         update: function () {
    151             return this;
    152         },
    153
    154         /**
    155          * Set the transformation matrix for different types of standard transforms.
    156          * @param {JXG.Board} board
    157          * @param {String} type   Transformation type, possible values are
    158          *                        'translate', 'scale', 'reflect', 'rotate',
    159          *                        'shear', 'generic'.
    160          * @param {Array} params Parameters for the various transformation types.
    161          *
    162          * <p>A transformation with a generic matrix looks like:
    163          * <pre>
    164          * ( a  b  c )   ( z )
    165          * ( d  e  f ) * ( x )
    166          * ( g  h  i )   ( y )
    167          * </pre>
    168          *
    169          * The transformation matrix then looks like:
    170          * <p>
    171          * Translation matrix:
    172          * <pre>
    173          * ( 1  0  0)   ( z )
    174          * ( a  1  0) * ( x )
    175          * ( b  0  1)   ( y )
    176          * </pre>
    177          *
    178          * <p>
    179          * Scale matrix:
    180          * <pre>
    181          * ( 1  0  0)   ( z )
    182          * ( 0  a  0) * ( x )
    183          * ( 0  0  b)   ( y )
    184          * </pre>
    185          *
    186          * <p>
    187          * A rotation matrix with angle a (in Radians)
    188          * <pre>
    189          * ( 1    0        0      )   ( z )
    190          * ( 0    cos(a)   -sin(a)) * ( x )
    191          * ( 0    sin(a)   cos(a) )   ( y )
    192          * </pre>
    193          *
    194          * <p>
    195          * Shear matrix:
    196          * <pre>
    197          * ( 1  0  0)   ( z )
    198          * ( 0  1  a) * ( x )
    199          * ( 0  b  1)   ( y )
    200          * </pre>
    201          *
    202          * <p>Generic transformation (9 parameters):
    203          * <pre>
    204          * ( a  b  c )   ( z )
    205          * ( d  e  f ) * ( x )
    206          * ( g  h  i )   ( y )
    207          * </pre>
    208          *
    209          * <p>Matrix:
    210          * <pre>
    211          * (         )   ( z )
    212          * (    M    ) * ( x )
    213          * (         )   ( y )
    214          * </pre>
    215          */
    216         setMatrix: function (board, type, params) {
    217             var i;
    218                 // e, obj; // Handle dependencies
    219
    220             this.isNumericMatrix = true;
    221             for (i = 0; i < params.length; i++) {
    222                 if (typeof params[i] !== "number") {
    223                     this.isNumericMatrix = false;
    224                     break;
    225                 }
    226             }
    227
    228             if (type === "translate") {
    229                 if (params.length !== 2) {
    230                     throw new Error("JSXGraph: translate transformation needs 2 parameters.");
    231                 }
    232                 this.evalParam = Type.createEvalFunction(board, params, 2);
    233                 this.update = function () {
    234                     this.matrix[1][0] = this.evalParam(0);
    235                     this.matrix[2][0] = this.evalParam(1);
    236                 };
    237             } else if (type === "scale") {
    238                 if (params.length !== 2) {
    239                     throw new Error("JSXGraph: scale transformation needs 2 parameters.");
    240                 }
    241                 this.evalParam = Type.createEvalFunction(board, params, 2);
    242                 this.update = function () {
    243                     this.matrix[1][1] = this.evalParam(0); // x
    244                     this.matrix[2][2] = this.evalParam(1); // y
    245                 };
    246                 // Input: line or two points
    247             } else if (type === "reflect") {
    248                 // line or two points
    249                 if (params.length < 4) {
    250                     params[0] = board.select(params[0]);
    251                 }
    252
    253                 // two points
    254                 if (params.length === 2) {
    255                     params[1] = board.select(params[1]);
    256                 }
    257
    258                 // 4 coordinates [px,py,qx,qy]
    259                 if (params.length === 4) {
    260                     this.evalParam = Type.createEvalFunction(board, params, 4);
    261                 }
    262
    263                 this.update = function () {
    264                     var x, y, z, xoff, yoff, d, v, p;
    265                     // Determine homogeneous coordinates of reflections axis
    266                     // line
    267                     if (params.length === 1) {
    268                         v = params[0].stdform;
    269                     } else if (params.length === 2) {
    270                         // two points
    271                         v = Mat.crossProduct(
    272                             params[1].coords.usrCoords,
    273                             params[0].coords.usrCoords
    274                         );
    275                     } else if (params.length === 4) {
    276                         // two points coordinates [px,py,qx,qy]
    277                         v = Mat.crossProduct(
    278                             [1, this.evalParam(2), this.evalParam(3)],
    279                             [1, this.evalParam(0), this.evalParam(1)]
    280                         );
    281                     }
    282
    283                     // Project origin to the line. This gives a finite point p
    284                     x = v[1];
    285                     y = v[2];
    286                     z = v[0];
    287                     p = [-z * x, -z * y, x * x + y * y];
    288                     d = p[2];
    289
    290                     // Normalize p
    291                     xoff = p[0] / p[2];
    292                     yoff = p[1] / p[2];
    293
    294                     // x, y is the direction of the line
    295                     x = -v[2];
    296                     y = v[1];
    297
    298                     this.matrix[1][1] = (x * x - y * y) / d;
    299                     this.matrix[1][2] = (2 * x * y) / d;
    300                     this.matrix[2][1] = this.matrix[1][2];
    301                     this.matrix[2][2] = -this.matrix[1][1];
    302                     this.matrix[1][0] =
    303                         xoff * (1 - this.matrix[1][1]) - yoff * this.matrix[1][2];
    304                     this.matrix[2][0] =
    305                         yoff * (1 - this.matrix[2][2]) - xoff * this.matrix[2][1];
    306                 };
    307             } else if (type === "rotate") {
    308                 if (params.length === 3) {
    309                     // angle, x, y
    310                     this.evalParam = Type.createEvalFunction(board, params, 3);
    311                 } else if (params.length > 0 && params.length <= 2) {
    312                     // angle, p or angle
    313                     this.evalParam = Type.createEvalFunction(board, params, 1);
    314
    315                     if (params.length === 2 && !Type.isArray(params[1])) {
    316                         params[1] = board.select(params[1]);
    317                     }
    318                 }
    319
    320                 this.update = function () {
    321                     var x,
    322                         y,
    323                         beta = this.evalParam(0),
    324                         co = Math.cos(beta),
    325                         si = Math.sin(beta);
    326
    327                     this.matrix[1][1] = co;
    328                     this.matrix[1][2] = -si;
    329                     this.matrix[2][1] = si;
    330                     this.matrix[2][2] = co;
    331
    332                     // rotate around [x,y] otherwise rotate around [0,0]
    333                     if (params.length > 1) {
    334                         if (params.length === 3) {
    335                             x = this.evalParam(1);
    336                             y = this.evalParam(2);
    337                         } else {
    338                             if (Type.isArray(params[1])) {
    339                                 x = params[1][0];
    340                                 y = params[1][1];
    341                             } else {
    342                                 x = params[1].X();
    343                                 y = params[1].Y();
    344                             }
    345                         }
    346                         this.matrix[1][0] = x * (1 - co) + y * si;
    347                         this.matrix[2][0] = y * (1 - co) - x * si;
    348                     }
    349                 };
    350             } else if (type === "shear") {
    351                 if (params.length !== 2) {
    352                     throw new Error("JSXGraph: shear transformation needs 2 parameters.");
    353                 }
    354
    355                 this.evalParam = Type.createEvalFunction(board, params, 2);
    356                 this.update = function () {
    357                     this.matrix[1][2] = this.evalParam(0);
    358                     this.matrix[2][1] = this.evalParam(1);
    359                 };
    360             } else if (type === "generic") {
    361                 if (params.length !== 9) {
    362                     throw new Error("JSXGraph: generic transformation needs 9 parameters.");
    363                 }
    364
    365                 this.evalParam = Type.createEvalFunction(board, params, 9);
    366
    367                 this.update = function () {
    368                     this.matrix[0][0] = this.evalParam(0);
    369                     this.matrix[0][1] = this.evalParam(1);
    370                     this.matrix[0][2] = this.evalParam(2);
    371                     this.matrix[1][0] = this.evalParam(3);
    372                     this.matrix[1][1] = this.evalParam(4);
    373                     this.matrix[1][2] = this.evalParam(5);
    374                     this.matrix[2][0] = this.evalParam(6);
    375                     this.matrix[2][1] = this.evalParam(7);
    376                     this.matrix[2][2] = this.evalParam(8);
    377                 };
    378             } else if (type === "matrix") {
    379                 if (params.length !== 1) {
    380                     throw new Error("JSXGraph: transformation of type 'matrix' needs 1 parameter.");
    381                 }
    382
    383                 this.evalParam = params[0].slice();
    384                 this.update = function () {
    385                     var i, j;
    386                     for (i = 0; i < 3; i++) {
    387                         for (j = 0; j < 3; j++) {
    388                             this.matrix[i][j] = Type.evaluate(this.evalParam[i][j]);
    389                         }
    390                     }
    391                 };
    392             }
    393
    394             // Handle dependencies
    395             // NO: transformations do not have method addParents
    396             // if (Type.exists(this.evalParam)) {
    397             //     for (e in this.evalParam.deps) {
    398             //         obj = this.evalParam.deps[e];
    399             //         this.addParents(obj);
    400             //         obj.addChild(this);
    401             //     }
    402             // }
    403         },
    404
    405         /**
    406          * Set the 3D transformation matrix for different types of standard transforms.
    407          * @param {JXG.Board} board
    408          * @param {String} type   Transformation type, possible values are
    409          *                        'translate', 'scale', 'rotate',
    410          *                        'rotateX', 'rotateY', 'rotateZ',
    411          *                        'shear', 'generic'.
    412          * @param {Array} params Parameters for the various transformation types.
    413          *
    414          * <p>A transformation with a generic matrix looks like:
    415          * <pre>
    416          * ( a  b  c  d)   ( w )
    417          * ( e  f  g  h) * ( x )
    418          * ( i  j  k  l)   ( y )
    419          * ( m  n  o  p)   ( z )
    420          * </pre>
    421          *
    422          * The transformation matrix then looks like:
    423          * <p>
    424          * Translation matrix:
    425          * <pre>
    426          * ( 1  0  0  0)   ( w )
    427          * ( a  1  0  0) * ( x )
    428          * ( b  0  1  0)   ( y )
    429          * ( c  0  0  1)   ( z )
    430          * </pre>
    431          *
    432          * <p>
    433          * Scale matrix:
    434          * <pre>
    435          * ( 1  0  0  0)   ( w )
    436          * ( 0  a  0  0) * ( x )
    437          * ( 0  0  b  0)   ( y )
    438          * ( 0  0  0  c)   ( z )
    439          * </pre>
    440          *
    441          * <p>
    442          * rotateX: a rotation matrix with angle a (in Radians)
    443          * <pre>
    444          * ( 1    0        0             )   ( z )
    445          * ( 0    1        0         0   ) * ( x )
    446          * ( 0    0      cos(a)   -sin(a)) * ( x )
    447          * ( 0    0      sin(a)   cos(a) )   ( y )
    448          * </pre>
    449          *
    450          * <p>
    451          * rotateY: a rotation matrix with angle a (in Radians)
    452          * <pre>
    453          * ( 1      0       0           )   ( z )
    454          * ( 0    cos(a)    0    -sin(a)) * ( x )
    455          * ( 0      0       1       0   ) * ( x )
    456          * ( 0    sin(a)    0    cos(a) )   ( y )
    457          * </pre>
    458          *
    459          * <p>
    460          * rotateZ: a rotation matrix with angle a (in Radians)
    461          * <pre>
    462          * ( 1      0                0  )   ( z )
    463          * ( 0    cos(a)   -sin(a)   0  ) * ( x )
    464          * ( 0    sin(a)   cos(a)    0  )   ( y )
    465          * ( 0      0         0      1  ) * ( x )
    466          * </pre>
    467          *
    468          * <p>
    469          * rotate: a rotation matrix with angle a (in Radians)
    470          * and normal <i>n</i>.
    471          *
    472          */
    473         setMatrix3D: function(view, type, params) {
    474             var i,
    475                 board = view.board;
    476
    477             this.isNumericMatrix = true;
    478             for (i = 0; i < params.length; i++) {
    479                 if (typeof params[i] !== "number") {
    480                     this.isNumericMatrix = false;
    481                     break;
    482                 }
    483             }
    484
    485             if (type === "translate") {
    486                 if (params.length !== 3) {
    487                     throw new Error("JSXGraph: 3D translate transformation needs 3 parameters.");
    488                 }
    489                 this.evalParam = Type.createEvalFunction(board, params, 3);
    490                 this.update = function () {
    491                     this.matrix[1][0] = this.evalParam(0);
    492                     this.matrix[2][0] = this.evalParam(1);
    493                     this.matrix[3][0] = this.evalParam(2);
    494                 };
    495             } else if (type === 'scale') {
    496                 if (params.length !== 3 && params.length !== 4) {
    497                     throw new Error("JSXGraph: 3D scale transformation needs either 3 or 4 parameters.");
    498                 }
    499                 this.evalParam = Type.createEvalFunction(board, params, 3);
    500                 this.update = function () {
    501                     var x = this.evalParam(0),
    502                         y = this.evalParam(1),
    503                         z = this.evalParam(2);
    504
    505                     this.matrix[1][1] = x;
    506                     this.matrix[2][2] = y;
    507                     this.matrix[3][3] = z;
    508                 };
    509             } else if (type === 'rotateX') {
    510                 if (params.length > 0 && params.length <= 2) {
    511                     this.evalParam = Type.createEvalFunction(board, params, 1);
    512                 }
    513
    514                 this.update = function () {
    515                     var a = this.evalParam(0),
    516                         co = Math.cos(a),
    517                         si = Math.sin(a);
    518
    519                     this.matrix[2][2] = co;
    520                     this.matrix[2][3] = -si;
    521                     this.matrix[3][2] = si;
    522                     this.matrix[3][3] = co;
    523                 };
    524             } else if (type === 'rotateY') {
    525                 if (params.length > 0 && params.length <= 2) {
    526                     this.evalParam = Type.createEvalFunction(board, params, 1);
    527                 }
    528                 this.update = function () {
    529                     var a = this.evalParam(0),
    530                         co = Math.cos(a),
    531                         si = Math.sin(a);
    532
    533                     this.matrix[1][1] = co;
    534                     this.matrix[1][3] = si;
    535                     this.matrix[3][1] = -si;
    536                     this.matrix[3][3] = co;
    537                 };
    538             } else if (type === 'rotateZ') {
    539                 if (params.length > 0 && params.length <= 2) {
    540                     this.evalParam = Type.createEvalFunction(board, params, 1);
    541                 }
    542                 this.update = function () {
    543                     var a = this.evalParam(0),
    544                         co = Math.cos(a),
    545                         si = Math.sin(a);
    546
    547                     this.matrix[1][1] = co;
    548                     this.matrix[1][2] = -si;
    549                     this.matrix[2][1] = si;
    550                     this.matrix[2][2] = co;
    551                 };
    552             } else if (type === 'rotate') {
    553                 if (params.length === 2) {
    554                     this.evalParam = Type.createEvalFunction(board, params, 2);
    555                 }
    556                 this.update = function () {
    557                     var a = this.evalParam(0), // angle
    558                         n = this.evalParam(1), // normal
    559                         co = Math.cos(a),
    560                         si = Math.sin(a),
    561                         n1, n2, n3,
    562                         nrm = Mat.norm(n);
    563
    564                     if (n.length === 3) {
    565                         n1 = n[0] / nrm;
    566                         n2 = n[1] / nrm;
    567                         n3 = n[2] / nrm;
    568                     } else {
    569                         n1 = n[1] / nrm;
    570                         n2 = n[2] / nrm;
    571                         n3 = n[3] / nrm;
    572                     }
    573
    574                     this.matrix = [
    575                         [1, 0, 0, 0],
    576                         [0, n1 * n1 * (1 - co) +      co, n1 * n2 * (1 - co) - n3 * si, n1 * n3 * (1 - co) + n2 * si],
    577                         [0, n2 * n1 * (1 - co) + n3 * si, n2 * n2 * (1 - co) +      co, n2 * n3 * (1 - co) - n1 * si],
    578                         [0, n3 * n1 * (1 - co) - n2 * si, n3 * n2 * (1 - co) + n1 * si, n3 * n3 * (1 - co) +      co]
    579                     ];
    580                 };
    581             }
    582         },
    583
    584         /**
    585          * Transform a point element, that are: {@link Point}, {@link Text}, {@link Image}, {@link Point3D}.
    586          * First, the transformation matrix is updated, then do the matrix-vector-multiplication.
    587          * <p>
    588          * Restricted to 2D transformations.
    589          *
    590          * @private
    591          * @param {JXG.GeometryElement} p element which is transformed
    592          * @param {String} 'self' Apply the transformation to the initialCoords instead of the coords if this is set.
    593          * @returns {Array}
    594          */
    595         apply: function (p, self) {
    596             var c;
    597
    598             this.update();
    599             if (this.is3D) {
    600                 c = p.coords;
    601             } else if (Type.exists(self)) {
    602                 c = p.initialCoords.usrCoords;
    603             } else {
    604                 c = p.coords.usrCoords;
    605             }
    606
    607             return Mat.matVecMult(this.matrix, c);
    608         },
    609
    610         /**
    611          * Applies a transformation once to a point element, that are: {@link Point}, {@link Text}, {@link Image}, {@link Point3D} or to an array of such elements.
    612          * If it is a free 2D point, then it can be dragged around later
    613          * and will overwrite the transformed coordinates.
    614          * @param {JXG.Point|Array} p
    615          */
    616         applyOnce: function (p) {
    617             var c, len, i;
    618
    619             if (!Type.isArray(p)) {
    620                 p = [p];
    621             }
    622
    623             len = p.length;
    624             for (i = 0; i < len; i++) {
    625                 this.update();
    626                 if (this.is3D) {
    627                     p[i].coords = Mat.matVecMult(this.matrix, p[i].coords);
    628                 } else {
    629                     c = Mat.matVecMult(this.matrix, p[i].coords.usrCoords);
    630                     p[i].coords.setCoordinates(Const.COORDS_BY_USER, c);
    631                 }
    632             }
    633         },
    634
    635         /**
    636          * Binds a transformation to a GeometryElement or an array of elements. In every update of the
    637          * GeometryElement(s), the transformation is executed. That means, in order to immediately
    638          * apply the transformation after calling bindTo, a call of board.update() has to follow.
    639          * <p>
    640          * The transformation is simply appended to the existing list of transformations of the object.
    641          * It is not fused (melt) with an existing transformation.
    642          *
    643          * @param  {Array|JXG.Object} el JXG.Object or array of JXG.Object to
    644          *                            which the transformation is bound to.
    645          * @see JXG.Transformation.meltTo
    646          */
    647         bindTo: function (el) {
    648             var i, len;
    649             if (Type.isArray(el)) {
    650                 len = el.length;
    651
    652                 for (i = 0; i < len; i++) {
    653                     el[i].transformations.push(this);
    654                 }
    655             } else {
    656                 el.transformations.push(this);
    657             }
    658         },
    659
    660         /**
    661          * Binds a transformation to a GeometryElement or an array of elements. In every update of the
    662          * GeometryElement(s), the transformation is executed. That means, in order to immediately
    663          * apply the transformation after calling meltTo, a call of board.update() has to follow.
    664          * <p>
    665          * In case the last transformation of the element and this transformation are static,
    666          * i.e. the transformation matrices do not depend on other elements,
    667          * the transformation will be fused into (multiplied with) the last transformation of
    668          * the element. Thus, the list of transformations is kept small.
    669          * If the transformation will be the first transformation ot the element, it will be cloned
    670          * to prevent side effects.
    671          *
    672          * @param  {Array|JXG.Object} el JXG.Object or array of JXG.Objects to
    673          *                            which the transformation is bound to.
    674          *
    675          * @see JXG.Transformation#bindTo
    676          */
    677         meltTo: function (el) {
    678             var i, elt, t;
    679
    680             if (Type.isArray(el)) {
    681                 for (i = 0; i < el.length; i++) {
    682                     this.meltTo(el[i]);
    683                 }
    684             } else {
    685                 elt = el.transformations;
    686                 if (elt.length > 0 &&
    687                     elt[elt.length - 1].isNumericMatrix &&
    688                     this.isNumericMatrix
    689                 ) {
    690                     elt[elt.length - 1].melt(this);
    691                 } else {
    692                     // Use a clone of the transformation.
    693                     // Otherwise, if the transformation is meltTo twice
    694                     // the transformation will be changed.
    695                     t = this.clone();
    696                     elt.push(t);
    697                 }
    698             }
    699         },
    700
    701         /**
    702          * Create a copy of the transformation in case it is static, i.e.
    703          * if the transformation matrix does not depend on other elements.
    704          * <p>
    705          * If the transformation matrix is not static, null will be returned.
    706          *
    707          * @returns {JXG.Transformation}
    708          */
    709         clone: function() {
    710             var t = null;
    711
    712             if (this.isNumericMatrix) {
    713                 t = new JXG.Transformation(this.board, 'none', []);
    714                 t.matrix = this.matrix.slice();
    715             }
    716
    717             return t;
    718         },
    719
    720         /**
    721          * Unused
    722          * @deprecated Use setAttribute
    723          * @param term
    724          */
    725         setProperty: function (term) {
    726             JXG.deprecated("Transformation.setProperty()", "Transformation.setAttribute()");
    727         },
    728
    729         /**
    730          * Empty method. Unused.
    731          * @param {Object} term Key-value pairs of the attributes.
    732          */
    733         setAttribute: function (term) {},
    734
    735         /**
    736          * Combine two transformations to one transformation. This only works if
    737          * both of transformation matrices consist of numbers solely, and do not
    738          * contain functions.
    739          *
    740          * Multiplies the transformation with a transformation t from the left.
    741          * i.e. (this) = (t) join (this)
    742          * @param  {JXG.Transform} t Transformation which is the left multiplicand
    743          * @returns {JXG.Transform} the transformation object.
    744          */
    745         melt: function (t) {
    746             var res = [];
    747
    748             this.update();
    749             t.update();
    750
    751             res = Mat.matMatMult(t.matrix, this.matrix);
    752
    753             this.update = function () {
    754                 this.matrix = res;
    755             };
    756
    757             return this;
    758         },
    759
    760         // Documented in element.js
    761         // Not yet, since transformations are not listed in board.objects.
    762         getParents: function () {
    763             var p = [[].concat.apply([], this.matrix)];
    764
    765             if (this.parents.length !== 0) {
    766                 p = this.parents;
    767             }
    768
    769             return p;
    770         }
    771     }
    772 );
    773
    774 /**
    775  * @class Define projective 2D transformations like translation, rotation, reflection.
    776  * @pseudo
    777  * @description A transformation consists of a 3x3 matrix, i.e. it is a projective transformation.
    778  * <p>
    779  * Internally, a transformation is applied to an element by multiplying the 3x3 matrix from the left to
    780  * the homogeneous coordinates of the element. JSXGraph represents homogeneous coordinates in the order
    781  * (z, x, y). The matrix has the form
    782  * <pre>
    783  * ( a  b  c )   ( z )
    784  * ( d  e  f ) * ( x )
    785  * ( g  h  i )   ( y )
    786  * </pre>
    787  * where in general a=1. If b = c = 0, the transformation is called <i>affine</i>.
    788  * In this case, finite points will stay finite. This is not the case for general projective coordinates.
    789  * <p>
    790  * Transformations acting on texts and images are considered to be affine, i.e. b and c are ignored.
    791  *
    792  * @name Transformation
    793  * @augments JXG.Transformation
    794  * @constructor
    795  * @type JXG.Transformation
    796  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    797  * @param {number|function|JXG.GeometryElement} parameters The parameters depend on the transformation type, supplied as attribute 'type'.
    798  * Possible transformation types are
    799  * <ul>
    800  * <li> 'translate'
    801  * <li> 'scale'
    802  * <li> 'reflect'
    803  * <li> 'rotate'
    804  * <li> 'shear'
    805  * <li> 'generic'
    806  * <li> 'matrix'
    807  * </ul>
    808  * <p>Valid parameters for these types are:
    809  * <dl>
    810  * <dt><b><tt>type:"translate"</tt></b></dt><dd><b>x, y</b> Translation vector (two numbers or functions).
    811  * The transformation matrix for x = a and y = b has the form:
    812  * <pre>
    813  * ( 1  0  0)   ( z )
    814  * ( a  1  0) * ( x )
    815  * ( b  0  1)   ( y )
    816  * </pre>
    817  * </dd>
    818  * <dt><b><tt>type:"scale"</tt></b></dt><dd><b>scale_x, scale_y</b> Scale vector (two numbers or functions).
    819  * The transformation matrix for scale_x = a and scale_y = b has the form:
    820  * <pre>
    821  * ( 1  0  0)   ( z )
    822  * ( 0  a  0) * ( x )
    823  * ( 0  0  b)   ( y )
    824  * </pre>
    825  * </dd>
    826  * <dt><b><tt>type:"rotate"</tt></b></dt><dd> <b>alpha, [point | x, y]</b> The parameters are the angle value in Radians
    827  *     (a number or function), and optionally a coordinate pair (two numbers or functions) or a point element defining the
    828  *                rotation center. If the rotation center is not given, the transformation rotates around (0,0).
    829  * The transformation matrix for angle a and rotating around (0, 0) has the form:
    830  * <pre>
    831  * ( 1    0        0      )   ( z )
    832  * ( 0    cos(a)   -sin(a)) * ( x )
    833  * ( 0    sin(a)   cos(a) )   ( y )
    834  * </pre>
    835  * </dd>
    836  * <dt><b><tt>type:"shear"</tt></b></dt><dd><b>shear_x, shear_y</b> Shear vector (two numbers or functions).
    837  * The transformation matrix for shear_x = a and shear_y = b has the form:
    838  * <pre>
    839  * ( 1  0  0)   ( z )
    840  * ( 0  1  a) * ( x )
    841  * ( 0  b  1)   ( y )
    842  * </pre>
    843  * </dd>
    844  * <dt><b><tt>type:"reflect"</tt></b></dt><dd>The parameters can either be:
    845  *    <ul>
    846  *      <li> <b>line</b> a line element,
    847  *      <li> <b>p, q</b> two point elements,
    848  *      <li> <b>p_x, p_y, q_x, q_y</b> four numbers or functions  determining a line through points (p_x, p_y) and (q_x, q_y).
    849  *    </ul>
    850  * </dd>
    851  * <dt><b><tt>type:"generic"</tt></b></dt><dd><b>a, b, c, d, e, f, g, h, i</b> Nine matrix entries (numbers or functions)
    852  *  for a generic projective transformation.
    853  * The matrix has the form
    854  * <pre>
    855  * ( a  b  c )   ( z )
    856  * ( d  e  f ) * ( x )
    857  * ( g  h  i )   ( y )
    858  * </pre>
    859  * </dd>
    860  * <dt><b><tt>type:"matrix"</tt></b></dt><dd><b>M</b> 3x3 transformation matrix containing numbers or functions</dd>
    861  * </dl>
    862  *
    863  *
    864  * @see JXG.Transformation#setMatrix
    865  *
    866  * @example
    867  * // The point B is determined by taking twice the vector A from the origin
    868  *
    869  * var p0 = board.create('point', [0, 3], {name: 'A'}),
    870  *     t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type: 'translate'}),
    871  *     p1 = board.create('point', [p0, t], {color: 'blue'});
    872  *
    873  * </pre><div class="jxgbox" id="JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    874  * <script type="text/javascript">
    875  *     (function() {
    876  *         var board = JXG.JSXGraph.initBoard('JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723',
    877  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    878  *     var p0 = board.create('point', [0, 3], {name: 'A'}),
    879  *         t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type:'translate'}),
    880  *         p1 = board.create('point', [p0, t], {color: 'blue'});
    881  *
    882  *     })();
    883  *
    884  * </script><pre>
    885  *
    886  * @example
    887  * // The point B is the result of scaling the point A with factor 2 in horizontal direction
    888  * // and with factor 0.5 in vertical direction.
    889  *
    890  * var p1 = board.create('point', [1, 1]),
    891  *     t = board.create('transform', [2, 0.5], {type: 'scale'}),
    892  *     p2 = board.create('point', [p1, t], {color: 'blue'});
    893  *
    894  * </pre><div class="jxgbox" id="JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    895  * <script type="text/javascript">
    896  *     (function() {
    897  *         var board = JXG.JSXGraph.initBoard('JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723',
    898  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    899  *     var p1 = board.create('point', [1, 1]),
    900  *         t = board.create('transform', [2, 0.5], {type: 'scale'}),
    901  *         p2 = board.create('point', [p1, t], {color: 'blue'});
    902  *
    903  *     })();
    904  *
    905  * </script><pre>
    906  *
    907  * @example
    908  * // The point B is rotated around C which gives point D. The angle is determined
    909  * // by the vertical height of point A.
    910  *
    911  * var p0 = board.create('point', [0, 3], {name: 'A'}),
    912  *     p1 = board.create('point', [1, 1]),
    913  *     p2 = board.create('point', [2, 1], {name:'C', fixed: true}),
    914  *
    915  *     // angle, rotation center:
    916  *     t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}),
    917  *     p3 = board.create('point', [p1, t], {color: 'blue'});
    918  *
    919  * </pre><div class="jxgbox" id="JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    920  * <script type="text/javascript">
    921  *     (function() {
    922  *         var board = JXG.JSXGraph.initBoard('JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723',
    923  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    924  *     var p0 = board.create('point', [0, 3], {name: 'A'}),
    925  *         p1 = board.create('point', [1, 1]),
    926  *         p2 = board.create('point', [2, 1], {name:'C', fixed: true}),
    927  *
    928  *         // angle, rotation center:
    929  *         t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}),
    930  *         p3 = board.create('point', [p1, t], {color: 'blue'});
    931  *
    932  *     })();
    933  *
    934  * </script><pre>
    935  *
    936  * @example
    937  * // A concatenation of several transformations.
    938  * var p1 = board.create('point', [1, 1]),
    939  *     t1 = board.create('transform', [-2, -1], {type: 'translate'}),
    940  *     t2 = board.create('transform', [Math.PI/4], {type: 'rotate'}),
    941  *     t3 = board.create('transform', [2, 1], {type: 'translate'}),
    942  *     p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'});
    943  *
    944  * </pre><div class="jxgbox" id="JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    945  * <script type="text/javascript">
    946  *     (function() {
    947  *         var board = JXG.JSXGraph.initBoard('JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723',
    948  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    949  *     var p1 = board.create('point', [1, 1]),
    950  *         t1 = board.create('transform', [-2, -1], {type:'translate'}),
    951  *         t2 = board.create('transform', [Math.PI/4], {type:'rotate'}),
    952  *         t3 = board.create('transform', [2, 1], {type:'translate'}),
    953  *         p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'});
    954  *
    955  *     })();
    956  *
    957  * </script><pre>
    958  *
    959  * @example
    960  * // Reflection of point A
    961  * var p1 = board.create('point', [1, 1]),
    962  *     p2 = board.create('point', [1, 3]),
    963  *     p3 = board.create('point', [-2, 0]),
    964  *     l = board.create('line', [p2, p3]),
    965  *     t = board.create('transform', [l], {type: 'reflect'}),  // Possible are l, l.id, l.name
    966  *     p4 = board.create('point', [p1, t], {color: 'blue'});
    967  *
    968  * </pre><div class="jxgbox" id="JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    969  * <script type="text/javascript">
    970  *     (function() {
    971  *         var board = JXG.JSXGraph.initBoard('JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723',
    972  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    973  *     var p1 = board.create('point', [1, 1]),
    974  *         p2 = board.create('point', [1, 3]),
    975  *         p3 = board.create('point', [-2, 0]),
    976  *         l = board.create('line', [p2, p3]),
    977  *         t = board.create('transform', [l], {type:'reflect'}),  // Possible are l, l.id, l.name
    978  *         p4 = board.create('point', [p1, t], {color: 'blue'});
    979  *
    980  *     })();
    981  *
    982  * </script><pre>
    983  *
    984  * @example
    985  * // Type: 'matrix'
    986  *         var y = board.create('slider', [[-3, 1], [-3, 4], [0, 1, 6]]);
    987  *         var t1 = board.create('transform', [
    988  *             [
    989  *                 [1, 0, 0],
    990  *                 [0, 1, 0],
    991  *                 [() => y.Value(), 0, 1]
    992  *             ]
    993  *         ], {type: 'matrix'});
    994  *
    995  *         var A = board.create('point', [2, -3]);
    996  *         var B = board.create('point', [A, t1]);
    997  *
    998  * </pre><div id="JXGd2bfd46c-3c0c-45c5-a92b-583fad0eb3ec" class="jxgbox" style="width: 300px; height: 300px;"></div>
    999  * <script type="text/javascript">
    1000  *     (function() {
    1001  *         var board = JXG.JSXGraph.initBoard('JXGd2bfd46c-3c0c-45c5-a92b-583fad0eb3ec',
    1002  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    1003  *             var y = board.create('slider', [[-3, 1], [-3, 4], [0, 1, 6]]);
    1004  *             var t1 = board.create('transform', [
    1005  *                 [
    1006  *                     [1, 0, 0],
    1007  *                     [0, 1, 0],
    1008  *                     [() => y.Value(), 0, 1]
    1009  *                 ]
    1010  *             ], {type: 'matrix'});
    1011  *
    1012  *             var A = board.create('point', [2, -3]);
    1013  *             var B = board.create('point', [A, t1]);
    1014  *
    1015  *     })();
    1016  *
    1017  * </script><pre>
    1018  *
    1019  * @example
    1020  * // One time application of a transform to points A, B
    1021  * var p1 = board.create('point', [1, 1]),
    1022  *     p2 = board.create('point', [-1, -2]),
    1023  *     t = board.create('transform', [3, 2], {type: 'shear'});
    1024  * t.applyOnce([p1, p2]);
    1025  *
    1026  * </pre><div class="jxgbox" id="JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    1027  * <script type="text/javascript">
    1028  *     (function() {
    1029  *         var board = JXG.JSXGraph.initBoard('JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723',
    1030  *             {boundingbox: [-8, 8, 8, -8], axis: true, showcopyright: false, shownavigation: false});
    1031  *     var p1 = board.create('point', [1, 1]),
    1032  *         p2 = board.create('point', [-1, -2]),
    1033  *         t = board.create('transform', [3, 2], {type: 'shear'});
    1034  *     t.applyOnce([p1, p2]);
    1035  *
    1036  *     })();
    1037  *
    1038  * </script><pre>
    1039  *
    1040  * @example
    1041  * // Construct a square of side length 2 with the
    1042  * // help of transformations
    1043  *     var sq = [],
    1044  *         right = board.create('transform', [2, 0], {type: 'translate'}),
    1045  *         up = board.create('transform', [0, 2], {type: 'translate'}),
    1046  *         pol, rot, p0;
    1047  *
    1048  *     // The first point is free
    1049  *     sq[0] = board.create('point', [0, 0], {name: 'Drag me'}),
    1050  *
    1051  *     // Construct the other free points by transformations
    1052  *     sq[1] = board.create('point', [sq[0], right]),
    1053  *     sq[2] = board.create('point', [sq[0], [right, up]]),
    1054  *     sq[3] = board.create('point', [sq[0], up]),
    1055  *
    1056  *     // Polygon through these four points
    1057  *     pol = board.create('polygon', sq, {
    1058  *             fillColor:'blue',
    1059  *             gradient:'radial',
    1060  *             gradientsecondcolor:'white',
    1061  *             gradientSecondOpacity:'0'
    1062  *     }),
    1063  *
    1064  *     p0 = board.create('point', [0, 3], {name: 'angle'}),
    1065  *     // Rotate the square around point sq[0] by dragging A vertically.
    1066  *     rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'});
    1067  *
    1068  *     // Apply the rotation to all but the first point of the square
    1069  *     rot.bindTo(sq.slice(1));
    1070  *
    1071  * </pre><div class="jxgbox" id="JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    1072  * <script type="text/javascript">
    1073  *     (function() {
    1074  *         var board = JXG.JSXGraph.initBoard('JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723',
    1075  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    1076  *     // Construct a square of side length 2 with the
    1077  *     // help of transformations
    1078  *     var sq = [],
    1079  *         right = board.create('transform', [2, 0], {type: 'translate'}),
    1080  *         up = board.create('transform', [0, 2], {type: 'translate'}),
    1081  *         pol, rot, p0;
    1082  *
    1083  *     // The first point is free
    1084  *     sq[0] = board.create('point', [0, 0], {name: 'Drag me'}),
    1085  *
    1086  *     // Construct the other free points by transformations
    1087  *     sq[1] = board.create('point', [sq[0], right]),
    1088  *     sq[2] = board.create('point', [sq[0], [right, up]]),
    1089  *     sq[3] = board.create('point', [sq[0], up]),
    1090  *
    1091  *     // Polygon through these four points
    1092  *     pol = board.create('polygon', sq, {
    1093  *             fillColor:'blue',
    1094  *             gradient:'radial',
    1095  *             gradientsecondcolor:'white',
    1096  *             gradientSecondOpacity:'0'
    1097  *     }),
    1098  *
    1099  *     p0 = board.create('point', [0, 3], {name: 'angle'}),
    1100  *     // Rotate the square around point sq[0] by dragging A vertically.
    1101  *     rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'});
    1102  *
    1103  *     // Apply the rotation to all but the first point of the square
    1104  *     rot.bindTo(sq.slice(1));
    1105  *
    1106  *     })();
    1107  *
    1108  * </script><pre>
    1109  *
    1110  * @example
    1111  * // Text transformation
    1112  * var p0 = board.create('point', [0, 0], {name: 'p_0'});
    1113  * var p1 = board.create('point', [3, 0], {name: 'p_1'});
    1114  * var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'});
    1115  *
    1116  * // If p_0 is dragged, translate p_1 and text accordingly
    1117  * var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'});
    1118  * tOff.bindTo(txt);
    1119  * tOff.bindTo(p1);
    1120  *
    1121  * // Rotate text around p_0 by dragging point p_1
    1122  * var tRot = board.create('transform', [
    1123  *     () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'});
    1124  * tRot.bindTo(txt);
    1125  *
    1126  * // Scale text by dragging point "p_1"
    1127  * // We do this by
    1128  * // - moving text by -p_0 (inverse of transformation tOff),
    1129  * // - scale the text (because scaling is relative to (0,0))
    1130  * // - move the text back by +p_0
    1131  * var tOffInv = board.create('transform', [
    1132  *         () => -p0.X(),
    1133  *         () => -p0.Y()
    1134  * ], {type:'translate'});
    1135  * var tScale = board.create('transform', [
    1136  *         // Some scaling factor
    1137  *         () => p1.Dist(p0) / 3,
    1138  *         () => p1.Dist(p0) / 3
    1139  * ], {type:'scale'});
    1140  * tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt);
    1141  *
    1142  * </pre><div id="JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1143  * <script type="text/javascript">
    1144  *     (function() {
    1145  *         var board = JXG.JSXGraph.initBoard('JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66',
    1146  *             {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false});
    1147  *     var p0 = board.create('point', [0, 0], {name: 'p_0'});
    1148  *     var p1 = board.create('point', [3, 0], {name: 'p_1'});
    1149  *     var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'});
    1150  *
    1151  *     // If p_0 is dragged, translate p_1 and text accordingly
    1152  *     var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'});
    1153  *     tOff.bindTo(txt);
    1154  *     tOff.bindTo(p1);
    1155  *
    1156  *     // Rotate text around p_0 by dragging point p_1
    1157  *     var tRot = board.create('transform', [
    1158  *         () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'});
    1159  *     tRot.bindTo(txt);
    1160  *
    1161  *     // Scale text by dragging point "p_1"
    1162  *     // We do this by
    1163  *     // - moving text by -p_0 (inverse of transformation tOff),
    1164  *     // - scale the text (because scaling is relative to (0,0))
    1165  *     // - move the text back by +p_0
    1166  *     var tOffInv = board.create('transform', [
    1167  *             () => -p0.X(),
    1168  *             () => -p0.Y()
    1169  *     ], {type:'translate'});
    1170  *     var tScale = board.create('transform', [
    1171  *             // Some scaling factor
    1172  *             () => p1.Dist(p0) / 3,
    1173  *             () => p1.Dist(p0) / 3
    1174  *     ], {type:'scale'});
    1175  *     tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt);
    1176  *
    1177  *     })();
    1178  *
    1179  * </script><pre>
    1180  *
    1181  */
    1182 JXG.createTransform = function (board, parents, attributes) {
    1183     return new JXG.Transformation(board, attributes.type, parents);
    1184 };
    1185
    1186 JXG.registerElement('transform', JXG.createTransform);
    1187
    1188 /**
    1189  * @class Define projective 3D transformations like translation, rotation, reflection.
    1190  * @pseudo
    1191  * @description A transformation consists of a 4x4 matrix, i.e. it is a projective transformation.
    1192  * <p>
    1193  * Internally, a transformation is applied to an element by multiplying the 4x4 matrix from the left to
    1194  * the homogeneous coordinates of the element. JSXGraph represents homogeneous coordinates in the order
    1195  * (w, x, y, z). If the coordinate is a finite point, w=1. The matrix has the form
    1196  * <pre>
    1197  * ( a b c d)   ( w )
    1198  * ( e f g h) * ( x )
    1199  * ( i j k l)   ( y )
    1200  * ( m n o p)   ( z )
    1201  * </pre>
    1202  * where in general a=1. If b = c = d = 0, the transformation is called <i>affine</i>.
    1203  * In this case, finite points will stay finite. This is not the case for general projective coordinates.
    1204  * <p>
    1205  *
    1206  * @name Transformation3D
    1207  * @augments JXG.Transformation
    1208  * @constructor
    1209  * @type JXG.Transformation
    1210  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1211  * @param {number|function|JXG.GeometryElement3D} parameters The parameters depend on the transformation type, supplied as attribute 'type'.
    1212  *  Possible transformation types are
    1213  * <ul>
    1214  * <li> 'translate'
    1215  * <li> 'scale'
    1216  * <li> 'rotate'
    1217  * <li> 'rotateX'
    1218  * <li> 'rotateY'
    1219  * <li> 'rotateZ'
    1220  * </ul>
    1221  * <p>Valid parameters for these types are:
    1222  * <dl>
    1223  * <dt><b><tt>type:"translate"</tt></b></dt><dd><b>x, y, z</b> Translation vector (three numbers or functions).
    1224  * The transformation matrix for x = a, y = b, and z = c has the form:
    1225  * <pre>
    1226  * ( 1  0  0  0)   ( w )
    1227  * ( a  1  0  0) * ( x )
    1228  * ( b  0  1  0)   ( y )
    1229  * ( c  0  0  c)   ( z )
    1230  * </pre>
    1231  * </dd>
    1232  * <dt><b><tt>type:"scale"</tt></b></dt><dd><b>scale_x, scale_y, scale_z</b> Scale vector (three numbers or functions).
    1233  * The transformation matrix for scale_x = a, scale_y = b, scale_z = c has the form:
    1234  * <pre>
    1235  * ( 1  0  0  0)   ( w )
    1236  * ( 0  a  0  0) * ( x )
    1237  * ( 0  0  b  0)   ( y )
    1238  * ( 0  0  0  c)   ( z )
    1239  * </pre>
    1240  * </dd>
    1241  * <dt><b><tt>type:"rotate"</tt></b></dt><dd><b>a, n</b> angle (in radians), normal.
    1242  * Rotate with angle a around the normal vector n.
    1243  * </dd>
    1244  * <dt><b><tt>type:"rotateX"</tt></b></dt><dd><b>a</b> angle (in radians).
    1245  * Rotate with angle a around the normal vector (1, 0, 0).
    1246  * </dd>
    1247  * <dt><b><tt>type:"rotateY"</tt></b></dt><dd><b>a</b> angle (in radians).
    1248  * Rotate with angle a around the normal vector (0, 1, 0).
    1249  * </dd>
    1250  * <dt><b><tt>type:"rotateZ"</tt></b></dt><dd><b>a</b> angle (in radians).
    1251  * Rotate with angle a around the normal vector (0, 0, 1).
    1252  * </dd>
    1253  * </dl>
    1254 */
    1255 JXG.createTransform3D = function (board, parents, attributes) {
    1256     return new JXG.Transformation(board, attributes.type, parents, true);
    1257 };
    1258
    1259 JXG.registerElement('transform3d', JXG.createTransform3D);
    1260
    1261 export default JXG.Transformation;
    1262
    1263