1 /*
  2     Copyright 2008-2026
  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                 params.splice(1, 0, [1, 0, 0]);
511                 this.setMatrix3D(view, 'rotate', params);
512             } else if (type === 'rotateY') {
513                 params.splice(1, 0, [0, 1, 0]);
514                 this.setMatrix3D(view, 'rotate', params);
515             } else if (type === 'rotateZ') {
516                 params.splice(1, 0, [0, 0, 1]);
517                 this.setMatrix3D(view, 'rotate', params);
518             } else if (type === 'rotate') {
519                 if (params.length < 2) {
520                     throw new Error("JSXGraph: 3D rotate transformation needs 2 or 3 parameters.");
521                 }
522                 if (params.length === 3 && !Type.isFunction(params[2]) && !Type.isArray(params[2])) {
523                     this.evalParam = Type.createEvalFunction(board, params, 2);
524                     params[2] = view.select(params[2]);
525                 } else {
526                     this.evalParam = Type.createEvalFunction(board, params, params.length);
527                 }
528                 this.update = function () {
529                     var a = this.evalParam(0), // angle
530                         n = this.evalParam(1), // normal
531                         p = [1, 0, 0, 0],
532                         co = Math.cos(a),
533                         si = Math.sin(a),
534                         n1, n2, n3,
535                         m1 = [
536                             [1, 0, 0, 0],
537                             [0, 1, 0, 0],
538                             [0, 0, 1, 0],
539                             [0, 0, 0, 1]
540                         ],
541                         m2 = [
542                             [1, 0, 0, 0],
543                             [0, 1, 0, 0],
544                             [0, 0, 1, 0],
545                             [0, 0, 0, 1]
546                         ],
547                         nrm = Mat.norm(n);
548 
549                     if (n.length === 3) {
550                         n1 = n[0] / nrm;
551                         n2 = n[1] / nrm;
552                         n3 = n[2] / nrm;
553                     } else {
554                         n1 = n[1] / nrm;
555                         n2 = n[2] / nrm;
556                         n3 = n[3] / nrm;
557                     }
558                     if (params.length === 3) {
559                         if (params.length === 3 && Type.exists(params[2].is3D)) {
560                             p = params[2].coords.slice();
561                         } else {
562                             p = this.evalParam(2);
563                         }
564                         if (p.length === 3) {
565                             p.unshift(1);
566                         }
567                         m1[1][0] = -p[1];
568                         m1[2][0] = -p[2];
569                         m1[3][0] = -p[3];
570 
571                         m2[1][0] = p[1];
572                         m2[2][0] = p[2];
573                         m2[3][0] = p[3];
574                     }
575 
576                     this.matrix = [
577                         [1, 0, 0, 0],
578                         [0, n1 * n1 * (1 - co) +      co, n1 * n2 * (1 - co) - n3 * si, n1 * n3 * (1 - co) + n2 * si],
579                         [0, n2 * n1 * (1 - co) + n3 * si, n2 * n2 * (1 - co) +      co, n2 * n3 * (1 - co) - n1 * si],
580                         [0, n3 * n1 * (1 - co) - n2 * si, n3 * n2 * (1 - co) + n1 * si, n3 * n3 * (1 - co) +      co]
581                     ];
582                     this.matrix = Mat.matMatMult(this.matrix, m1);
583                     this.matrix = Mat.matMatMult(m2, this.matrix);
584                 };
585             } else if (type === 'affine') {
586                 if (params.length !== 9) {
587                     throw new Error("JSXGraph: 3D transformation of type 'affine' needs 9 parameters.");
588                 }
589 
590                 this.evalParam = Type.createEvalFunction(board, params, 9);
591                 this.update = function () {
592                     this.matrix[1][1] = this.evalParam(0);
593                     this.matrix[1][2] = this.evalParam(1);
594                     this.matrix[1][3] = this.evalParam(2);
595                     this.matrix[2][1] = this.evalParam(3);
596                     this.matrix[2][2] = this.evalParam(4);
597                     this.matrix[2][3] = this.evalParam(5);
598                     this.matrix[3][1] = this.evalParam(6);
599                     this.matrix[3][2] = this.evalParam(7);
600                     this.matrix[3][3] = this.evalParam(8);
601                 };
602             } else if (type === 'affinematrix') {
603                 if (params.length !== 1) {
604                     throw new Error("JSXGraph: 3D transformation of type 'affinematrix' needs 1 parameter.");
605                 }
606 
607                 this.evalParam = params[0].slice();
608                 this.update = function () {
609                     var i, j;
610                     for (i = 0; i < 3; i++) {
611                         for (j = 0; j < 3; j++) {
612                             this.matrix[i + 1][j + 1] = Type.evaluate(this.evalParam[i][j]);
613                         }
614                     }
615                 };
616             } else if (type === 'generic') {
617                 if (params.length !== 16) {
618                     throw new Error("JSXGraph: 3D transformation of type 'generic' needs 16 parameters.");
619                 }
620 
621                 this.evalParam = Type.createEvalFunction(board, params, 6);
622                 this.update = function () {
623                     this.matrix[0][0] = this.evalParam(0);
624                     this.matrix[0][1] = this.evalParam(1);
625                     this.matrix[0][2] = this.evalParam(2);
626                     this.matrix[0][3] = this.evalParam(3);
627                     this.matrix[1][0] = this.evalParam(4);
628                     this.matrix[1][1] = this.evalParam(5);
629                     this.matrix[1][2] = this.evalParam(6);
630                     this.matrix[1][3] = this.evalParam(7);
631                     this.matrix[2][0] = this.evalParam(8);
632                     this.matrix[2][1] = this.evalParam(9);
633                     this.matrix[2][2] = this.evalParam(10);
634                     this.matrix[2][3] = this.evalParam(11);
635                     this.matrix[3][0] = this.evalParam(12);
636                     this.matrix[3][1] = this.evalParam(13);
637                     this.matrix[3][2] = this.evalParam(14);
638                     this.matrix[3][3] = this.evalParam(15);
639                 };
640             } else if (type === 'matrix') {
641                 if (params.length !== 1) {
642                     throw new Error("JSXGraph: 3D transformation of type 'matrix' needs 1 parameter.");
643                 }
644 
645                 this.evalParam = params[0].slice();
646                 this.update = function () {
647                     var i, j;
648                     for (i = 0; i < 4; i++) {
649                         for (j = 0; j < 4; j++) {
650                             this.matrix[i][j] = Type.evaluate(this.evalParam[i][j]);
651                         }
652                     }
653                 };
654             }
655         },
656 
657         /**
658          * Transform a point element, that are: {@link Point}, {@link Text}, {@link Image}, {@link Point3D}.
659          * First, the transformation matrix is updated, then do the matrix-vector-multiplication.
660          * <p>
661          * Restricted to 2D transformations.
662          *
663          * @private
664          * @param {JXG.GeometryElement} p element which is transformed
665          * @param {String} 'self' Apply the transformation to the initialCoords instead of the coords if this is set.
666          * @returns {Array}
667          */
668         apply: function (p, self) {
669             var c;
670 
671             this.update();
672             if (this.is3D) {
673                 c = p.coords;
674             } else if (Type.exists(self)) {
675                 c = p.initialCoords.usrCoords;
676             } else {
677                 c = p.coords.usrCoords;
678             }
679 
680             return Mat.matVecMult(this.matrix, c);
681         },
682 
683         /**
684          * 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.
685          * If it is a free 2D point, then it can be dragged around later
686          * and will overwrite the transformed coordinates.
687          * @param {JXG.Point|Array} p
688          */
689         applyOnce: function (p) {
690             var c, len, i;
691 
692             if (!Type.isArray(p)) {
693                 p = [p];
694             }
695 
696             len = p.length;
697             for (i = 0; i < len; i++) {
698                 this.update();
699                 if (this.is3D) {
700                     p[i].coords = Mat.matVecMult(this.matrix, p[i].coords);
701                 } else {
702                     c = Mat.matVecMult(this.matrix, p[i].coords.usrCoords);
703                     p[i].coords.setCoordinates(Const.COORDS_BY_USER, c);
704                 }
705             }
706         },
707 
708         /**
709          * Binds a transformation to a GeometryElement or an array of elements. In every update of the
710          * GeometryElement(s), the transformation is executed. That means, in order to immediately
711          * apply the transformation after calling bindTo, a call of board.update() has to follow.
712          * <p>
713          * The transformation is simply appended to the existing list of transformations of the object.
714          * It is not fused (melt) with an existing transformation.
715          *
716          * @param  {Array|JXG.Object} el JXG.Object or array of JXG.Object to
717          *                            which the transformation is bound to.
718          * @see JXG.Transformation.meltTo
719          */
720         bindTo: function (el) {
721             var i, len;
722             if (Type.isArray(el)) {
723                 len = el.length;
724 
725                 for (i = 0; i < len; i++) {
726                     el[i].transformations.push(this);
727                 }
728             } else {
729                 el.transformations.push(this);
730             }
731         },
732 
733         /**
734          * Binds a transformation to a GeometryElement or an array of elements. In every update of the
735          * GeometryElement(s), the transformation is executed. That means, in order to immediately
736          * apply the transformation after calling meltTo, a call of board.update() has to follow.
737          * <p>
738          * In case the last transformation of the element and this transformation are static,
739          * i.e. the transformation matrices do not depend on other elements,
740          * the transformation will be fused into (multiplied with) the last transformation of
741          * the element. Thus, the list of transformations is kept small.
742          * If the transformation will be the first transformation ot the element, it will be cloned
743          * to prevent side effects.
744          *
745          * @param  {Array|JXG.Object} el JXG.Object or array of JXG.Objects to
746          *                            which the transformation is bound to.
747          *
748          * @see JXG.Transformation#bindTo
749          */
750         meltTo: function (el) {
751             var i, elt, t;
752 
753             if (Type.isArray(el)) {
754                 for (i = 0; i < el.length; i++) {
755                     this.meltTo(el[i]);
756                 }
757             } else {
758                 elt = el.transformations;
759                 if (elt.length > 0 &&
760                     elt[elt.length - 1].isNumericMatrix &&
761                     this.isNumericMatrix
762                 ) {
763                     elt[elt.length - 1].melt(this);
764                 } else {
765                     // Use a clone of the transformation.
766                     // Otherwise, if the transformation is meltTo twice
767                     // the transformation will be changed.
768                     t = this.clone();
769                     elt.push(t);
770                 }
771             }
772         },
773 
774         /**
775          * Create a copy of the transformation in case it is static, i.e.
776          * if the transformation matrix does not depend on other elements.
777          * <p>
778          * If the transformation matrix is not static, null will be returned.
779          *
780          * @returns {JXG.Transformation}
781          */
782         clone: function() {
783             var t = null;
784 
785             if (this.isNumericMatrix) {
786                 t = new JXG.Transformation(this.board, 'none', []);
787                 t.matrix = this.matrix.slice();
788             }
789 
790             return t;
791         },
792 
793         /**
794          * Unused
795          * @deprecated Use setAttribute
796          * @param term
797          */
798         setProperty: function (term) {
799             JXG.deprecated("Transformation.setProperty()", "Transformation.setAttribute()");
800         },
801 
802         /**
803          * Empty method. Unused.
804          * @param {Object} term Key-value pairs of the attributes.
805          */
806         setAttribute: function (term) {},
807 
808         /**
809          * Combine two transformations to one transformation. This only works if
810          * both of transformation matrices consist of numbers solely, and do not
811          * contain functions.
812          *
813          * Multiplies the transformation with a transformation t from the left.
814          * i.e. (this) = (t) join (this)
815          * @param  {JXG.Transform} t Transformation which is the left multiplicand
816          * @returns {JXG.Transform} the transformation object.
817          */
818         melt: function (t) {
819             var res = [];
820 
821             this.update();
822             t.update();
823 
824             res = Mat.matMatMult(t.matrix, this.matrix);
825 
826             this.update = function () {
827                 this.matrix = res;
828             };
829 
830             return this;
831         },
832 
833         // Documented in element.js
834         // Not yet, since transformations are not listed in board.objects.
835         getParents: function () {
836             var p = [[].concat.apply([], this.matrix)];
837 
838             if (this.parents.length !== 0) {
839                 p = this.parents;
840             }
841 
842             return p;
843         }
844     }
845 );
846 
847 /**
848  * @class Define projective 2D transformations like translation, rotation, reflection.
849  * @pseudo
850  * @description A transformation consists of a 3x3 matrix, i.e. it is a projective transformation.
851  * <p>
852  * Internally, a transformation is applied to an element by multiplying the 3x3 matrix from the left to
853  * the homogeneous coordinates of the element. JSXGraph represents homogeneous coordinates in the order
854  * (z, x, y). The matrix has the form
855  * <pre>
856  * ( a  b  c )   ( z )
857  * ( d  e  f ) * ( x )
858  * ( g  h  i )   ( y )
859  * </pre>
860  * where in general a=1. If b = c = 0, the transformation is called <i>affine</i>.
861  * In this case, finite points will stay finite. This is not the case for general projective coordinates.
862  * <p>
863  * Transformations acting on texts and images are considered to be affine, i.e. b and c are ignored.
864  *
865  * @name Transformation
866  * @augments JXG.Transformation
867  * @constructor
868  * @type JXG.Transformation
869  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
870  * @param {number|function|JXG.GeometryElement} parameters The parameters depend on the transformation type, supplied as attribute 'type'.
871  * Possible transformation types are
872  * <ul>
873  * <li> 'translate'
874  * <li> 'scale'
875  * <li> 'reflect'
876  * <li> 'rotate'
877  * <li> 'shear'
878  * <li> 'generic'
879  * <li> 'matrix'
880  * </ul>
881  * <p>Valid parameters for these types are:
882  * <dl>
883  * <dt><b><tt>type:"translate"</tt></b></dt><dd><b>x, y</b> Translation vector (two numbers or functions).
884  * The transformation matrix for x = a and y = b has the form:
885  * <pre>
886  * ( 1  0  0)   ( z )
887  * ( a  1  0) * ( x )
888  * ( b  0  1)   ( y )
889  * </pre>
890  * </dd>
891  * <dt><b><tt>type:"scale"</tt></b></dt><dd><b>scale_x, scale_y</b> Scale vector (two numbers or functions).
892  * The transformation matrix for scale_x = a and scale_y = b has the form:
893  * <pre>
894  * ( 1  0  0)   ( z )
895  * ( 0  a  0) * ( x )
896  * ( 0  0  b)   ( y )
897  * </pre>
898  * </dd>
899  * <dt><b><tt>type:"rotate"</tt></b></dt><dd> <b>alpha, [point | x, y]</b> The parameters are the angle value in Radians
900  *     (a number or function), and optionally a coordinate pair (two numbers or functions) or a point element defining the
901  *                rotation center. If the rotation center is not given, the transformation rotates around (0,0).
902  * The transformation matrix for angle a and rotating around (0, 0) has the form:
903  * <pre>
904  * ( 1    0        0      )   ( z )
905  * ( 0    cos(a)   -sin(a)) * ( x )
906  * ( 0    sin(a)   cos(a) )   ( y )
907  * </pre>
908  * </dd>
909  * <dt><b><tt>type:"shear"</tt></b></dt><dd><b>shear_x, shear_y</b> Shear vector (two numbers or functions).
910  * The transformation matrix for shear_x = a and shear_y = b has the form:
911  * <pre>
912  * ( 1  0  0)   ( z )
913  * ( 0  1  a) * ( x )
914  * ( 0  b  1)   ( y )
915  * </pre>
916  * </dd>
917  * <dt><b><tt>type:"reflect"</tt></b></dt><dd>The parameters can either be:
918  *    <ul>
919  *      <li> <b>line</b> a line element,
920  *      <li> <b>p, q</b> two point elements,
921  *      <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).
922  *    </ul>
923  * </dd>
924  * <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)
925  *  for a generic projective transformation.
926  * The matrix has the form
927  * <pre>
928  * ( a  b  c )   ( z )
929  * ( d  e  f ) * ( x )
930  * ( g  h  i )   ( y )
931  * </pre>
932  * </dd>
933  * <dt><b><tt>type:"matrix"</tt></b></dt><dd><b>M</b> 3x3 transformation matrix containing numbers or functions</dd>
934  * </dl>
935  *
936  *
937  * @see JXG.Transformation#setMatrix
938  *
939  * @example
940  * // The point B is determined by taking twice the vector A from the origin
941  *
942  * var p0 = board.create('point', [0, 3], {name: 'A'}),
943  *     t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type: 'translate'}),
944  *     p1 = board.create('point', [p0, t], {color: 'blue'});
945  *
946  * </pre><div class="jxgbox" id="JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
947  * <script type="text/javascript">
948  *     (function() {
949  *         var board = JXG.JSXGraph.initBoard('JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723',
950  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
951  *     var p0 = board.create('point', [0, 3], {name: 'A'}),
952  *         t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type:'translate'}),
953  *         p1 = board.create('point', [p0, t], {color: 'blue'});
954  *
955  *     })();
956  *
957  * </script><pre>
958  *
959  * @example
960  * // The point B is the result of scaling the point A with factor 2 in horizontal direction
961  * // and with factor 0.5 in vertical direction.
962  *
963  * var p1 = board.create('point', [1, 1]),
964  *     t = board.create('transform', [2, 0.5], {type: 'scale'}),
965  *     p2 = board.create('point', [p1, t], {color: 'blue'});
966  *
967  * </pre><div class="jxgbox" id="JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
968  * <script type="text/javascript">
969  *     (function() {
970  *         var board = JXG.JSXGraph.initBoard('JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723',
971  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
972  *     var p1 = board.create('point', [1, 1]),
973  *         t = board.create('transform', [2, 0.5], {type: 'scale'}),
974  *         p2 = board.create('point', [p1, t], {color: 'blue'});
975  *
976  *     })();
977  *
978  * </script><pre>
979  *
980  * @example
981  * // The point B is rotated around C which gives point D. The angle is determined
982  * // by the vertical height of point A.
983  *
984  * var p0 = board.create('point', [0, 3], {name: 'A'}),
985  *     p1 = board.create('point', [1, 1]),
986  *     p2 = board.create('point', [2, 1], {name:'C', fixed: true}),
987  *
988  *     // angle, rotation center:
989  *     t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}),
990  *     p3 = board.create('point', [p1, t], {color: 'blue'});
991  *
992  * </pre><div class="jxgbox" id="JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
993  * <script type="text/javascript">
994  *     (function() {
995  *         var board = JXG.JSXGraph.initBoard('JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723',
996  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
997  *     var p0 = board.create('point', [0, 3], {name: 'A'}),
998  *         p1 = board.create('point', [1, 1]),
999  *         p2 = board.create('point', [2, 1], {name:'C', fixed: true}),
1000  *
1001  *         // angle, rotation center:
1002  *         t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}),
1003  *         p3 = board.create('point', [p1, t], {color: 'blue'});
1004  *
1005  *     })();
1006  *
1007  * </script><pre>
1008  *
1009  * @example
1010  * // A concatenation of several transformations.
1011  * var p1 = board.create('point', [1, 1]),
1012  *     t1 = board.create('transform', [-2, -1], {type: 'translate'}),
1013  *     t2 = board.create('transform', [Math.PI/4], {type: 'rotate'}),
1014  *     t3 = board.create('transform', [2, 1], {type: 'translate'}),
1015  *     p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'});
1016  *
1017  * </pre><div class="jxgbox" id="JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
1018  * <script type="text/javascript">
1019  *     (function() {
1020  *         var board = JXG.JSXGraph.initBoard('JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723',
1021  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1022  *     var p1 = board.create('point', [1, 1]),
1023  *         t1 = board.create('transform', [-2, -1], {type:'translate'}),
1024  *         t2 = board.create('transform', [Math.PI/4], {type:'rotate'}),
1025  *         t3 = board.create('transform', [2, 1], {type:'translate'}),
1026  *         p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'});
1027  *
1028  *     })();
1029  *
1030  * </script><pre>
1031  *
1032  * @example
1033  * // Reflection of point A
1034  * var p1 = board.create('point', [1, 1]),
1035  *     p2 = board.create('point', [1, 3]),
1036  *     p3 = board.create('point', [-2, 0]),
1037  *     l = board.create('line', [p2, p3]),
1038  *     t = board.create('transform', [l], {type: 'reflect'}),  // Possible are l, l.id, l.name
1039  *     p4 = board.create('point', [p1, t], {color: 'blue'});
1040  *
1041  * </pre><div class="jxgbox" id="JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
1042  * <script type="text/javascript">
1043  *     (function() {
1044  *         var board = JXG.JSXGraph.initBoard('JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723',
1045  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1046  *     var p1 = board.create('point', [1, 1]),
1047  *         p2 = board.create('point', [1, 3]),
1048  *         p3 = board.create('point', [-2, 0]),
1049  *         l = board.create('line', [p2, p3]),
1050  *         t = board.create('transform', [l], {type:'reflect'}),  // Possible are l, l.id, l.name
1051  *         p4 = board.create('point', [p1, t], {color: 'blue'});
1052  *
1053  *     })();
1054  *
1055  * </script><pre>
1056  *
1057  * @example
1058  * // Type: 'matrix'
1059  *         var y = board.create('slider', [[-3, 1], [-3, 4], [0, 1, 6]]);
1060  *         var t1 = board.create('transform', [
1061  *             [
1062  *                 [1, 0, 0],
1063  *                 [0, 1, 0],
1064  *                 [() => y.Value(), 0, 1]
1065  *             ]
1066  *         ], {type: 'matrix'});
1067  *
1068  *         var A = board.create('point', [2, -3]);
1069  *         var B = board.create('point', [A, t1]);
1070  *
1071  * </pre><div id="JXGd2bfd46c-3c0c-45c5-a92b-583fad0eb3ec" class="jxgbox" style="width: 300px; height: 300px;"></div>
1072  * <script type="text/javascript">
1073  *     (function() {
1074  *         var board = JXG.JSXGraph.initBoard('JXGd2bfd46c-3c0c-45c5-a92b-583fad0eb3ec',
1075  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1076  *             var y = board.create('slider', [[-3, 1], [-3, 4], [0, 1, 6]]);
1077  *             var t1 = board.create('transform', [
1078  *                 [
1079  *                     [1, 0, 0],
1080  *                     [0, 1, 0],
1081  *                     [() => y.Value(), 0, 1]
1082  *                 ]
1083  *             ], {type: 'matrix'});
1084  *
1085  *             var A = board.create('point', [2, -3]);
1086  *             var B = board.create('point', [A, t1]);
1087  *
1088  *     })();
1089  *
1090  * </script><pre>
1091  *
1092  * @example
1093  * // One time application of a transform to points A, B
1094  * var p1 = board.create('point', [1, 1]),
1095  *     p2 = board.create('point', [-1, -2]),
1096  *     t = board.create('transform', [3, 2], {type: 'shear'});
1097  * t.applyOnce([p1, p2]);
1098  *
1099  * </pre><div class="jxgbox" id="JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
1100  * <script type="text/javascript">
1101  *     (function() {
1102  *         var board = JXG.JSXGraph.initBoard('JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723',
1103  *             {boundingbox: [-8, 8, 8, -8], axis: true, showcopyright: false, shownavigation: false});
1104  *     var p1 = board.create('point', [1, 1]),
1105  *         p2 = board.create('point', [-1, -2]),
1106  *         t = board.create('transform', [3, 2], {type: 'shear'});
1107  *     t.applyOnce([p1, p2]);
1108  *
1109  *     })();
1110  *
1111  * </script><pre>
1112  *
1113  * @example
1114  * // Construct a square of side length 2 with the
1115  * // help of transformations
1116  *     var sq = [],
1117  *         right = board.create('transform', [2, 0], {type: 'translate'}),
1118  *         up = board.create('transform', [0, 2], {type: 'translate'}),
1119  *         pol, rot, p0;
1120  *
1121  *     // The first point is free
1122  *     sq[0] = board.create('point', [0, 0], {name: 'Drag me'}),
1123  *
1124  *     // Construct the other free points by transformations
1125  *     sq[1] = board.create('point', [sq[0], right]),
1126  *     sq[2] = board.create('point', [sq[0], [right, up]]),
1127  *     sq[3] = board.create('point', [sq[0], up]),
1128  *
1129  *     // Polygon through these four points
1130  *     pol = board.create('polygon', sq, {
1131  *             fillColor:'blue',
1132  *             gradient:'radial',
1133  *             gradientsecondcolor:'white',
1134  *             gradientSecondOpacity:'0'
1135  *     }),
1136  *
1137  *     p0 = board.create('point', [0, 3], {name: 'angle'}),
1138  *     // Rotate the square around point sq[0] by dragging A vertically.
1139  *     rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'});
1140  *
1141  *     // Apply the rotation to all but the first point of the square
1142  *     rot.bindTo(sq.slice(1));
1143  *
1144  * </pre><div class="jxgbox" id="JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
1145  * <script type="text/javascript">
1146  *     (function() {
1147  *         var board = JXG.JSXGraph.initBoard('JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723',
1148  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1149  *     // Construct a square of side length 2 with the
1150  *     // help of transformations
1151  *     var sq = [],
1152  *         right = board.create('transform', [2, 0], {type: 'translate'}),
1153  *         up = board.create('transform', [0, 2], {type: 'translate'}),
1154  *         pol, rot, p0;
1155  *
1156  *     // The first point is free
1157  *     sq[0] = board.create('point', [0, 0], {name: 'Drag me'}),
1158  *
1159  *     // Construct the other free points by transformations
1160  *     sq[1] = board.create('point', [sq[0], right]),
1161  *     sq[2] = board.create('point', [sq[0], [right, up]]),
1162  *     sq[3] = board.create('point', [sq[0], up]),
1163  *
1164  *     // Polygon through these four points
1165  *     pol = board.create('polygon', sq, {
1166  *             fillColor:'blue',
1167  *             gradient:'radial',
1168  *             gradientsecondcolor:'white',
1169  *             gradientSecondOpacity:'0'
1170  *     }),
1171  *
1172  *     p0 = board.create('point', [0, 3], {name: 'angle'}),
1173  *     // Rotate the square around point sq[0] by dragging A vertically.
1174  *     rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'});
1175  *
1176  *     // Apply the rotation to all but the first point of the square
1177  *     rot.bindTo(sq.slice(1));
1178  *
1179  *     })();
1180  *
1181  * </script><pre>
1182  *
1183  * @example
1184  * // Text transformation
1185  * var p0 = board.create('point', [0, 0], {name: 'p_0'});
1186  * var p1 = board.create('point', [3, 0], {name: 'p_1'});
1187  * var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'});
1188  *
1189  * // If p_0 is dragged, translate p_1 and text accordingly
1190  * var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'});
1191  * tOff.bindTo(txt);
1192  * tOff.bindTo(p1);
1193  *
1194  * // Rotate text around p_0 by dragging point p_1
1195  * var tRot = board.create('transform', [
1196  *     () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'});
1197  * tRot.bindTo(txt);
1198  *
1199  * // Scale text by dragging point "p_1"
1200  * // We do this by
1201  * // - moving text by -p_0 (inverse of transformation tOff),
1202  * // - scale the text (because scaling is relative to (0,0))
1203  * // - move the text back by +p_0
1204  * var tOffInv = board.create('transform', [
1205  *         () => -p0.X(),
1206  *         () => -p0.Y()
1207  * ], {type:'translate'});
1208  * var tScale = board.create('transform', [
1209  *         // Some scaling factor
1210  *         () => p1.Dist(p0) / 3,
1211  *         () => p1.Dist(p0) / 3
1212  * ], {type:'scale'});
1213  * tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt);
1214  *
1215  * </pre><div id="JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66" class="jxgbox" style="width: 300px; height: 300px;"></div>
1216  * <script type="text/javascript">
1217  *     (function() {
1218  *         var board = JXG.JSXGraph.initBoard('JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66',
1219  *             {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false});
1220  *     var p0 = board.create('point', [0, 0], {name: 'p_0'});
1221  *     var p1 = board.create('point', [3, 0], {name: 'p_1'});
1222  *     var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'});
1223  *
1224  *     // If p_0 is dragged, translate p_1 and text accordingly
1225  *     var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'});
1226  *     tOff.bindTo(txt);
1227  *     tOff.bindTo(p1);
1228  *
1229  *     // Rotate text around p_0 by dragging point p_1
1230  *     var tRot = board.create('transform', [
1231  *         () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'});
1232  *     tRot.bindTo(txt);
1233  *
1234  *     // Scale text by dragging point "p_1"
1235  *     // We do this by
1236  *     // - moving text by -p_0 (inverse of transformation tOff),
1237  *     // - scale the text (because scaling is relative to (0,0))
1238  *     // - move the text back by +p_0
1239  *     var tOffInv = board.create('transform', [
1240  *             () => -p0.X(),
1241  *             () => -p0.Y()
1242  *     ], {type:'translate'});
1243  *     var tScale = board.create('transform', [
1244  *             // Some scaling factor
1245  *             () => p1.Dist(p0) / 3,
1246  *             () => p1.Dist(p0) / 3
1247  *     ], {type:'scale'});
1248  *     tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt);
1249  *
1250  *     })();
1251  *
1252  * </script><pre>
1253  *
1254  */
1255 JXG.createTransform = function (board, parents, attributes) {
1256     return new JXG.Transformation(board, attributes.type, parents);
1257 };
1258 
1259 JXG.registerElement('transform', JXG.createTransform);
1260 
1261 /**
1262  * @class Define projective 3D transformations like translation, rotation, reflection.
1263  * @pseudo
1264  * @description A transformation consists of a 4x4 matrix, i.e. it is a projective transformation.
1265  * <p>
1266  * Internally, a transformation is applied to an element by multiplying the 4x4 matrix from the left to
1267  * the homogeneous coordinates of the element. JSXGraph represents homogeneous coordinates in the order
1268  * (w, x, y, z). If the coordinate is a finite point, w=1. The matrix has the form
1269  * <pre>
1270  * ( a b c d)   ( w )
1271  * ( e f g h) * ( x )
1272  * ( i j k l)   ( y )
1273  * ( m n o p)   ( z )
1274  * </pre>
1275  * where in general a=1. If b = c = d = 0, the transformation is called <i>affine</i>.
1276  * In this case, finite points will stay finite. This is not the case for general projective coordinates.
1277  * <p>
1278  *
1279  * @name Transformation3D
1280  * @augments JXG.Transformation
1281  * @constructor
1282  * @type JXG.Transformation
1283  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
1284  * @param {number|function|JXG.GeometryElement3D} parameters The parameters depend on the transformation type, supplied as attribute 'type'.
1285  *  Possible transformation types are
1286  * <ul>
1287  * <li> 'translate'
1288  * <li> 'scale'
1289  * <li> 'rotate'
1290  * <li> 'rotateX'
1291  * <li> 'rotateY'
1292  * <li> 'rotateZ'
1293  * </ul>
1294  * <p>Valid parameters for these types are:
1295  * <dl>
1296  * <dt><b><tt>type:"translate"</tt></b></dt><dd><b>x, y, z</b> Translation vector (three numbers or functions).
1297  * The transformation matrix for x = a, y = b, and z = c has the form:
1298  * <pre>
1299  * ( 1  0  0  0)   ( w )
1300  * ( a  1  0  0) * ( x )
1301  * ( b  0  1  0)   ( y )
1302  * ( c  0  0  c)   ( z )
1303  * </pre>
1304  * </dd>
1305  * <dt><b><tt>type:"scale"</tt></b></dt><dd><b>scale_x, scale_y, scale_z</b> Scale vector (three numbers or functions).
1306  * The transformation matrix for scale_x = a, scale_y = b, scale_z = c has the form:
1307  * <pre>
1308  * ( 1  0  0  0)   ( w )
1309  * ( 0  a  0  0) * ( x )
1310  * ( 0  0  b  0)   ( y )
1311  * ( 0  0  0  c)   ( z )
1312  * </pre>
1313  * </dd>
1314  * <dt><b><tt>type:"rotate"</tt></b></dt><dd><b>a, n, [p=[0,0,0]]</b> angle (in radians), normal, [point].
1315  * Rotate with angle a around the normal vector n through the point p.
1316  * </dd>
1317  * <dt><b><tt>type:"rotateX"</tt></b></dt><dd><b>a, [p=[0,0,0]]</b> angle (in radians), [point].
1318  * Rotate with angle a around the normal vector (1, 0, 0) through the point p.
1319  * </dd>
1320  * <dt><b><tt>type:"rotateY"</tt></b></dt><dd><b>a, [p=[0,0,0]]</b> angle (in radians), [point].
1321  * Rotate with angle a around the normal vector (0, 1, 0) through the point p.
1322  * </dd>
1323  * <dt><b><tt>type:"rotateZ"</tt></b></dt><dd><b>a, [p=[0,0,0]]</b> angle (in radians), [point].
1324  * Rotate with angle a around the normal vector (0, 0, 1) through the point p.
1325  * </dd>
1326  * </dl>
1327  *
1328  * @example
1329  * var bound = [-5, 5];
1330  * var view = board.create('view3d',
1331  *     [
1332  *         [-5, -5], [8, 8],
1333  *         [bound, bound, bound]
1334  *     ], {
1335  *         projection: "central",
1336  *         depthOrder: { enabled: true },
1337  *         axesPosition: 'border' // 'center', 'none'
1338  *     }
1339  * );
1340  *
1341  * var slider = board.create('slider', [[-4, 6], [0, 6], [0, 0, 5]]);
1342  *
1343  * var p1 = view.create('point3d', [1, 2, 2], { name: 'drag me', size: 5 });
1344  *
1345  * // Translate from p1 by fixed amount
1346  * var t1 = view.create('transform3d', [2, 3, 2], { type: 'translate' });
1347  * // Translate from p1 by dynamic amount
1348  * var t2 = view.create('transform3d', [() => slider.Value() + 3, 0, 0], { type: 'translate' });
1349  *
1350  * view.create('point3d', [p1, t1], { name: 'translate fixed', size: 5 });
1351  * view.create('point3d', [p1, t2], { name: 'translate by func', size: 5 });
1352  *
1353  * </pre><div id="JXG2409bb0a-90d7-4c1e-ae9f-85e8a776acec" class="jxgbox" style="width: 300px; height: 300px;"></div>
1354  * <script type="text/javascript">
1355  *     (function() {
1356  *         var board = JXG.JSXGraph.initBoard('JXG2409bb0a-90d7-4c1e-ae9f-85e8a776acec',
1357  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1358  *     var bound = [-5, 5];
1359  *     var view = board.create('view3d',
1360  *         [
1361  *             [-5, -5], [8, 8],
1362  *             [bound, bound, bound]
1363  *         ], {
1364  *             projection: "central",
1365  *             depthOrder: { enabled: true },
1366  *             axesPosition: 'border' // 'center', 'none'
1367  *         }
1368  *     );
1369  *
1370  *     var slider = board.create('slider', [[-4, 6], [0, 6], [0, 0, 5]]);
1371  *
1372  *     var p1 = view.create('point3d', [1, 2, 2], { name: 'drag me', size: 5 });
1373  *
1374  *     // Translate from p1 by fixed amount
1375  *     var t1 = view.create('transform3d', [2, 3, 2], { type: 'translate' });
1376  *     // Translate from p1 by dynamic amount
1377  *     var t2 = view.create('transform3d', [() => slider.Value() + 3, 0, 0], { type: 'translate' });
1378  *
1379  *     view.create('point3d', [p1, t1], { name: 'translate fixed', size: 5 });
1380  *     view.create('point3d', [p1, t2], { name: 'translate by func', size: 5 });
1381  *
1382  *     })();
1383  *
1384  * </script><pre>
1385  *
1386  */
1387 JXG.createTransform3D = function (board, parents, attributes) {
1388     return new JXG.Transformation(board, attributes.type, parents, true);
1389 };
1390 
1391 JXG.registerElement('transform3d', JXG.createTransform3D);
1392 
1393 export default JXG.Transformation;
1394 
1395