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 (2D) 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 affine transformation (4 parameters):
 95  * <pre>
 96  * ( 1  0  0 )   ( z )
 97  * ( 0  a  b ) * ( x )
 98  * ( 0  c  d )   ( y )
 99  * </pre>
100  *
101  * <p>Affine 2x2 matrix:
102  * <pre>
103  * ( 1  0  0 )   ( z )
104  * ( 0  M    ) * ( x )
105  * ( 0       )   ( y )
106  * </pre>
107  *
108  * <p>Generic transformation (9 parameters):
109  * <pre>
110  * ( a  b  c )   ( z )
111  * ( d  e  f ) * ( x )
112  * ( g  h  i )   ( y )
113  * </pre>
114  *
115  * <p>3x3 Matrix:
116  * <pre>
117  * (         )   ( z )
118  * (    M    ) * ( x )
119  * (         )   ( y )
120  * </pre>
121  */
122 JXG.Transformation = function (board, type, params, is3D) {
123     this.elementClass = Const.OBJECT_CLASS_OTHER;
124     this.type = Const.OBJECT_TYPE_TRANSFORMATION;
125 
126     if (is3D) {
127         this.is3D = true;
128         this.matrix = [
129             [1, 0, 0, 0],
130             [0, 1, 0, 0],
131             [0, 0, 1, 0],
132             [0, 0, 0, 1]
133         ];
134     } else {
135         this.is3D = false;
136         this.matrix = [
137             [1, 0, 0],
138             [0, 1, 0],
139             [0, 0, 1]
140         ];
141     }
142 
143     this.board = board;
144     this.isNumericMatrix = false;
145     if (this.is3D) {
146         this.setMatrix3D(params[0] /* view3d */, type, params.slice(1));
147     } else {
148         this.setMatrix(board, type, params);
149     }
150 
151     this.methodMap = {
152         apply: "apply",
153         applyOnce: "applyOnce",
154         bindTo: "bindTo",
155         bind: "bindTo",
156         melt: "melt",
157         meltTo: "meltTo"
158     };
159 };
160 
161 JXG.Transformation.prototype = {};
162 
163 JXG.extend(
164     JXG.Transformation.prototype,
165     /** @lends JXG.Transformation.prototype */ {
166         /**
167          * Updates the numerical data for the transformation, i.e. the entry of the subobject matrix.
168          * @returns {JXG.Transform} returns pointer to itself
169          */
170         update: function () {
171             return this;
172         },
173 
174         /**
175          * Set the transformation matrix for different types of standard transforms.
176          * @param {JXG.Board} board
177          * @param {String} type   Transformation type, possible values are
178          *                        'translate', 'scale', 'reflect', 'rotate',
179          *                        'shear', 'generic'.
180          * @param {Array} params Parameters for the various transformation types.
181          *
182          * <p>A transformation with a generic matrix looks like:
183          * <pre>
184          * ( a  b  c )   ( z )
185          * ( d  e  f ) * ( x )
186          * ( g  h  i )   ( y )
187          * </pre>
188          *
189          * The transformation matrix then looks like:
190          * <p>
191          * Translation matrix:
192          * <pre>
193          * ( 1  0  0)   ( z )
194          * ( a  1  0) * ( x )
195          * ( b  0  1)   ( y )
196          * </pre>
197          *
198          * <p>
199          * Scale matrix:
200          * <pre>
201          * ( 1  0  0)   ( z )
202          * ( 0  a  0) * ( x )
203          * ( 0  0  b)   ( y )
204          * </pre>
205          *
206          * <p>
207          * A rotation matrix with angle a (in Radians)
208          * <pre>
209          * ( 1    0        0      )   ( z )
210          * ( 0    cos(a)  -sin(a) ) * ( x )
211          * ( 0    sin(a)   cos(a) )   ( y )
212          * </pre>
213          *
214          * <p>
215          * Shear matrix:
216          * <pre>
217          * ( 1  0  0)   ( z )
218          * ( 0  1  a) * ( x )
219          * ( 0  b  1)   ( y )
220          * </pre>
221          *
222          * <p>Generic affine transformation (4 parameters):
223          * <pre>
224          * ( 1  0  0 )   ( z )
225          * ( 0  a  b ) * ( x )
226          * ( 0  c  d )   ( y )
227          * </pre>
228          *
229          * <p>Affine 2x2 matrix:
230          * <pre>
231          * ( 1  0  0 )   ( z )
232          * ( 0  M    ) * ( x )
233          * ( 0       )   ( y )
234          * </pre>
235          *
236          * <p>Generic transformation (9 parameters):
237          * <pre>
238          * ( a  b  c )   ( z )
239          * ( d  e  f ) * ( x )
240          * ( g  h  i )   ( y )
241          * </pre>
242          *
243          * <p>3x3 Matrix:
244          * <pre>
245          * (         )   ( z )
246          * (    M    ) * ( x )
247          * (         )   ( y )
248          * </pre>
249          */
250         setMatrix: function (board, type, params) {
251             var i;
252                 // e, obj; // Handle dependencies
253 
254             this.isNumericMatrix = true;
255             for (i = 0; i < params.length; i++) {
256                 if (typeof params[i] !== 'number') {
257                     this.isNumericMatrix = false;
258                     break;
259                 }
260             }
261 
262             if (type === 'translate') {
263                 if (params.length !== 2) {
264                     throw new Error("JSXGraph: translate transformation needs 2 parameters.");
265                 }
266                 this.evalParam = Type.createEvalFunction(board, params, 2);
267                 this.update = function () {
268                     this.matrix[1][0] = this.evalParam(0);
269                     this.matrix[2][0] = this.evalParam(1);
270                 };
271             } else if (type === 'scale') {
272                 if (params.length !== 2) {
273                     throw new Error("JSXGraph: scale transformation needs 2 parameters.");
274                 }
275                 this.evalParam = Type.createEvalFunction(board, params, 2);
276                 this.update = function () {
277                     this.matrix[1][1] = this.evalParam(0); // x
278                     this.matrix[2][2] = this.evalParam(1); // y
279                 };
280                 // Input: line or two points
281             } else if (type === 'reflect') {
282                 // line or two points
283                 if (params.length < 4) {
284                     params[0] = board.select(params[0]);
285                 }
286 
287                 // two points
288                 if (params.length === 2) {
289                     params[1] = board.select(params[1]);
290                 }
291 
292                 // 4 coordinates [px,py,qx,qy]
293                 if (params.length === 4) {
294                     this.evalParam = Type.createEvalFunction(board, params, 4);
295                 }
296 
297                 this.update = function () {
298                     var x, y, z, xoff, yoff, d, v, p;
299                     // Determine homogeneous coordinates of reflections axis
300                     // line
301                     if (params.length === 1) {
302                         v = params[0].stdform;
303                     } else if (params.length === 2) {
304                         // two points
305                         v = Mat.crossProduct(
306                             params[1].coords.usrCoords,
307                             params[0].coords.usrCoords
308                         );
309                     } else if (params.length === 4) {
310                         // two points coordinates [px,py,qx,qy]
311                         v = Mat.crossProduct(
312                             [1, this.evalParam(2), this.evalParam(3)],
313                             [1, this.evalParam(0), this.evalParam(1)]
314                         );
315                     }
316 
317                     // Project origin to the line. This gives a finite point p
318                     x = v[1];
319                     y = v[2];
320                     z = v[0];
321                     p = [-z * x, -z * y, x * x + y * y];
322                     d = p[2];
323 
324                     // Normalize p
325                     xoff = p[0] / p[2];
326                     yoff = p[1] / p[2];
327 
328                     // x, y is the direction of the line
329                     x = -v[2];
330                     y = v[1];
331 
332                     this.matrix[1][1] = (x * x - y * y) / d;
333                     this.matrix[1][2] = (2 * x * y) / d;
334                     this.matrix[2][1] = this.matrix[1][2];
335                     this.matrix[2][2] = -this.matrix[1][1];
336                     this.matrix[1][0] =
337                         xoff * (1 - this.matrix[1][1]) - yoff * this.matrix[1][2];
338                     this.matrix[2][0] =
339                         yoff * (1 - this.matrix[2][2]) - xoff * this.matrix[2][1];
340                 };
341             } else if (type === 'rotate') {
342                 if (params.length === 3) {
343                     // angle, x, y
344                     this.evalParam = Type.createEvalFunction(board, params, 3);
345                 } else if (params.length > 0 && params.length <= 2) {
346                     // angle, p or angle
347                     this.evalParam = Type.createEvalFunction(board, params, 1);
348 
349                     if (params.length === 2 && !Type.isArray(params[1])) {
350                         params[1] = board.select(params[1]);
351                     }
352                 }
353 
354                 this.update = function () {
355                     var x,
356                         y,
357                         beta = this.evalParam(0),
358                         co = Math.cos(beta),
359                         si = Math.sin(beta);
360 
361                     this.matrix[1][1] = co;
362                     this.matrix[1][2] = -si;
363                     this.matrix[2][1] = si;
364                     this.matrix[2][2] = co;
365 
366                     // rotate around [x,y] otherwise rotate around [0,0]
367                     if (params.length > 1) {
368                         if (params.length === 3) {
369                             x = this.evalParam(1);
370                             y = this.evalParam(2);
371                         } else {
372                             if (Type.isArray(params[1])) {
373                                 x = params[1][0];
374                                 y = params[1][1];
375                             } else {
376                                 x = params[1].X();
377                                 y = params[1].Y();
378                             }
379                         }
380                         this.matrix[1][0] = x * (1 - co) + y * si;
381                         this.matrix[2][0] = y * (1 - co) - x * si;
382                     }
383                 };
384             } else if (type === 'shear') {
385                 if (params.length !== 2) {
386                     throw new Error("JSXGraph: shear transformation needs 2 parameters.");
387                 }
388 
389                 this.evalParam = Type.createEvalFunction(board, params, 2);
390                 this.update = function () {
391                     this.matrix[1][2] = this.evalParam(0);
392                     this.matrix[2][1] = this.evalParam(1);
393                 };
394             } else if (type === 'affine') {
395                 if (params.length !== 4) {
396                     throw new Error("JSXGraph: affine transformation needs 4 parameters.");
397                 }
398 
399                 this.evalParam = Type.createEvalFunction(board, params, 9);
400 
401                 this.update = function () {
402                     this.matrix[1][1] = this.evalParam(0);
403                     this.matrix[1][2] = this.evalParam(1);
404                     this.matrix[2][1] = this.evalParam(2);
405                     this.matrix[2][2] = this.evalParam(3);
406                 };
407             } else if (type === 'affinematrix') {
408                 if (params.length !== 1) {
409                     throw new Error("JSXGraph: transformation of type 'matrix' needs 1 parameter.");
410                 }
411 
412                 this.evalParam = params[0].slice();
413                 this.update = function () {
414                     var i, j;
415                     for (i = 0; i < 2; i++) {
416                         for (j = 0; j < 2; j++) {
417                             this.matrix[i + 1][j + 1] = Type.evaluate(this.evalParam[i][j]);
418                         }
419                     }
420                 };
421             } else if (type === 'generic') {
422                 if (params.length !== 9) {
423                     throw new Error("JSXGraph: generic transformation needs 9 parameters.");
424                 }
425 
426                 this.evalParam = Type.createEvalFunction(board, params, 9);
427 
428                 this.update = function () {
429                     this.matrix[0][0] = this.evalParam(0);
430                     this.matrix[0][1] = this.evalParam(1);
431                     this.matrix[0][2] = this.evalParam(2);
432                     this.matrix[1][0] = this.evalParam(3);
433                     this.matrix[1][1] = this.evalParam(4);
434                     this.matrix[1][2] = this.evalParam(5);
435                     this.matrix[2][0] = this.evalParam(6);
436                     this.matrix[2][1] = this.evalParam(7);
437                     this.matrix[2][2] = this.evalParam(8);
438                 };
439             } else if (type === 'matrix') {
440                 if (params.length !== 1) {
441                     throw new Error("JSXGraph: transformation of type 'matrix' needs 1 parameter.");
442                 }
443 
444                 this.evalParam = params[0].slice();
445                 this.update = function () {
446                     var i, j;
447                     for (i = 0; i < 3; i++) {
448                         for (j = 0; j < 3; j++) {
449                             this.matrix[i][j] = Type.evaluate(this.evalParam[i][j]);
450                         }
451                     }
452                 };
453             }
454 
455             // Handle dependencies
456             // NO: transformations do not have method addParents
457             // if (Type.exists(this.evalParam)) {
458             //     for (e in this.evalParam.deps) {
459             //         obj = this.evalParam.deps[e];
460             //         this.addParents(obj);
461             //         obj.addChild(this);
462             //     }
463             // }
464         },
465 
466         /**
467          * Set the 3D transformation matrix for different types of standard transforms.
468          * @param {JXG.Board} board
469          * @param {String} type   Transformation type, possible values are
470          *                        'translate', 'scale', 'rotate',
471          *                        'rotateX', 'rotateY', 'rotateZ',
472          *                        'shear', 'generic'.
473          * @param {Array} params Parameters for the various transformation types.
474          *
475          * <p>A transformation with a generic matrix looks like:
476          * <pre>
477          * ( a  b  c  d)   ( w )
478          * ( e  f  g  h) * ( x )
479          * ( i  j  k  l)   ( y )
480          * ( m  n  o  p)   ( z )
481          * </pre>
482          *
483          * The transformation matrix then looks like:
484          * <p>
485          * Translation matrix:
486          * <pre>
487          * ( 1  0  0  0)   ( w )
488          * ( a  1  0  0) * ( x )
489          * ( b  0  1  0)   ( y )
490          * ( c  0  0  1)   ( z )
491          * </pre>
492          *
493          * <p>
494          * Scale matrix:
495          * <pre>
496          * ( 1  0  0  0)   ( w )
497          * ( 0  a  0  0) * ( x )
498          * ( 0  0  b  0)   ( y )
499          * ( 0  0  0  c)   ( z )
500          * </pre>
501          *
502          * <p>
503          * rotateX: a rotation matrix with angle a (in Radians)
504          * <pre>
505          * ( 1    0        0             )   ( w )
506          * ( 0    1        0         0   ) * ( x )
507          * ( 0    0      cos(a)  -sin(a) )   ( y )
508          * ( 0    0      sin(a)   cos(a) )   ( z )
509          * </pre>
510          *
511          * <p>
512          * rotateY: a rotation matrix with angle a (in Radians)
513          * <pre>
514          * ( 1      0       0           )   ( w )
515          * ( 0    cos(a)    0   -sin(a) ) * ( x )
516          * ( 0      0       1       0   )   ( y )
517          * ( 0    sin(a)    0    cos(a) )   ( z )
518          * </pre>
519          *
520          * <p>
521          * rotateZ: a rotation matrix with angle a (in Radians)
522          * <pre>
523          * ( 1      0                0  )   ( w )
524          * ( 0    cos(a)  -sin(a)    0  ) * ( x )
525          * ( 0    sin(a)   cos(a)    0  )   ( y )
526          * ( 0      0         0      1  )   ( z )
527          * </pre>
528          *
529          * <p>
530          * rotate: a rotation matrix with angle a (in Radians)
531          * and normal <i>n</i>.
532          *
533          * <p>Generic affine transformation (9 parameters):
534          * <pre>
535          * ( 1  0  0  0 )   ( w )
536          * ( 0  a  b  c ) * ( x )
537          * ( 0  d  e  f )   ( y )
538          * ( 0  g  h  i )   ( z )
539          * </pre>
540          *
541          * <p>Affine 3x3 matrix:
542          * <pre>
543          * ( 1  0  0  0 )   ( w )
544          * ( 0          ) * ( x )
545          * ( 0     M    )   ( y )
546          * ( 0          )   ( z )
547          * </pre>
548          *
549          * <p>Generic transformation (16 parameters):
550          * <pre>
551          * ( a  b  c  d )   ( w )
552          * ( e  f  ...  ) * ( x )
553          * (    ...     )   ( y )
554          * (    ...   p )   ( z )
555          * </pre>
556          *
557          * <p>Generic 4x4 matrix:
558          * <pre>
559          * (            )   ( w )
560          * (     M      ) * ( x )
561          * (            )   ( y )
562          * (            )   ( z )
563          * </pre>
564          *
565          */
566         setMatrix3D: function(view, type, params) {
567             var i,
568                 board = view.board;
569 
570             this.isNumericMatrix = true;
571             for (i = 0; i < params.length; i++) {
572                 if (typeof params[i] !== 'number') {
573                     this.isNumericMatrix = false;
574                     break;
575                 }
576             }
577 
578             if (type === 'translate') {
579                 if (params.length !== 3) {
580                     throw new Error("JSXGraph: 3D translate transformation needs 3 parameters.");
581                 }
582                 this.evalParam = Type.createEvalFunction(board, params, 3);
583                 this.update = function () {
584                     this.matrix[1][0] = this.evalParam(0);
585                     this.matrix[2][0] = this.evalParam(1);
586                     this.matrix[3][0] = this.evalParam(2);
587                 };
588             } else if (type === 'scale') {
589                 if (params.length !== 3 && params.length !== 4) {
590                     throw new Error("JSXGraph: 3D scale transformation needs either 3 or 4 parameters.");
591                 }
592                 this.evalParam = Type.createEvalFunction(board, params, 3);
593                 this.update = function () {
594                     var x = this.evalParam(0),
595                         y = this.evalParam(1),
596                         z = this.evalParam(2);
597 
598                     this.matrix[1][1] = x;
599                     this.matrix[2][2] = y;
600                     this.matrix[3][3] = z;
601                 };
602             } else if (type === 'rotateX') {
603                 params.splice(1, 0, [1, 0, 0]);
604                 this.setMatrix3D(view, 'rotate', params);
605             } else if (type === 'rotateY') {
606                 params.splice(1, 0, [0, 1, 0]);
607                 this.setMatrix3D(view, 'rotate', params);
608             } else if (type === 'rotateZ') {
609                 params.splice(1, 0, [0, 0, 1]);
610                 this.setMatrix3D(view, 'rotate', params);
611             } else if (type === 'rotate') {
612                 if (params.length < 2) {
613                     throw new Error("JSXGraph: 3D rotate transformation needs 2 or 3 parameters.");
614                 }
615                 if (params.length === 3 && !Type.isFunction(params[2]) && !Type.isArray(params[2])) {
616                     this.evalParam = Type.createEvalFunction(board, params, 2);
617                     params[2] = view.select(params[2]);
618                 } else {
619                     this.evalParam = Type.createEvalFunction(board, params, params.length);
620                 }
621                 this.update = function () {
622                     var a = this.evalParam(0), // angle
623                         n = this.evalParam(1), // normal
624                         p = [1, 0, 0, 0],
625                         co = Math.cos(a),
626                         si = Math.sin(a),
627                         n1, n2, n3,
628                         m1 = [
629                             [1, 0, 0, 0],
630                             [0, 1, 0, 0],
631                             [0, 0, 1, 0],
632                             [0, 0, 0, 1]
633                         ],
634                         m2 = [
635                             [1, 0, 0, 0],
636                             [0, 1, 0, 0],
637                             [0, 0, 1, 0],
638                             [0, 0, 0, 1]
639                         ],
640                         nrm = Mat.norm(n);
641 
642                     if (n.length === 3) {
643                         n1 = n[0] / nrm;
644                         n2 = n[1] / nrm;
645                         n3 = n[2] / nrm;
646                     } else {
647                         n1 = n[1] / nrm;
648                         n2 = n[2] / nrm;
649                         n3 = n[3] / nrm;
650                     }
651                     if (params.length === 3) {
652                         if (params.length === 3 && Type.exists(params[2].is3D)) {
653                             p = params[2].coords.slice();
654                         } else {
655                             p = this.evalParam(2);
656                         }
657                         if (p.length === 3) {
658                             p.unshift(1);
659                         }
660                         m1[1][0] = -p[1];
661                         m1[2][0] = -p[2];
662                         m1[3][0] = -p[3];
663 
664                         m2[1][0] = p[1];
665                         m2[2][0] = p[2];
666                         m2[3][0] = p[3];
667                     }
668 
669                     this.matrix = [
670                         [1, 0, 0, 0],
671                         [0, n1 * n1 * (1 - co) +      co, n1 * n2 * (1 - co) - n3 * si, n1 * n3 * (1 - co) + n2 * si],
672                         [0, n2 * n1 * (1 - co) + n3 * si, n2 * n2 * (1 - co) +      co, n2 * n3 * (1 - co) - n1 * si],
673                         [0, n3 * n1 * (1 - co) - n2 * si, n3 * n2 * (1 - co) + n1 * si, n3 * n3 * (1 - co) +      co]
674                     ];
675                     this.matrix = Mat.matMatMult(this.matrix, m1);
676                     this.matrix = Mat.matMatMult(m2, this.matrix);
677                 };
678             } else if (type === 'affine') {
679                 if (params.length !== 9) {
680                     throw new Error("JSXGraph: 3D transformation of type 'affine' needs 9 parameters.");
681                 }
682 
683                 this.evalParam = Type.createEvalFunction(board, params, 9);
684                 this.update = function () {
685                     this.matrix[1][1] = this.evalParam(0);
686                     this.matrix[1][2] = this.evalParam(1);
687                     this.matrix[1][3] = this.evalParam(2);
688                     this.matrix[2][1] = this.evalParam(3);
689                     this.matrix[2][2] = this.evalParam(4);
690                     this.matrix[2][3] = this.evalParam(5);
691                     this.matrix[3][1] = this.evalParam(6);
692                     this.matrix[3][2] = this.evalParam(7);
693                     this.matrix[3][3] = this.evalParam(8);
694                 };
695             } else if (type === 'affinematrix') {
696                 if (params.length !== 1) {
697                     throw new Error("JSXGraph: 3D transformation of type 'affinematrix' needs 1 parameter.");
698                 }
699 
700                 this.evalParam = params[0].slice();
701                 this.update = function () {
702                     var i, j;
703                     for (i = 0; i < 3; i++) {
704                         for (j = 0; j < 3; j++) {
705                             this.matrix[i + 1][j + 1] = Type.evaluate(this.evalParam[i][j]);
706                         }
707                     }
708                 };
709             } else if (type === 'generic') {
710                 if (params.length !== 16) {
711                     throw new Error("JSXGraph: 3D transformation of type 'generic' needs 16 parameters.");
712                 }
713 
714                 this.evalParam = Type.createEvalFunction(board, params, 6);
715                 this.update = function () {
716                     this.matrix[0][0] = this.evalParam(0);
717                     this.matrix[0][1] = this.evalParam(1);
718                     this.matrix[0][2] = this.evalParam(2);
719                     this.matrix[0][3] = this.evalParam(3);
720                     this.matrix[1][0] = this.evalParam(4);
721                     this.matrix[1][1] = this.evalParam(5);
722                     this.matrix[1][2] = this.evalParam(6);
723                     this.matrix[1][3] = this.evalParam(7);
724                     this.matrix[2][0] = this.evalParam(8);
725                     this.matrix[2][1] = this.evalParam(9);
726                     this.matrix[2][2] = this.evalParam(10);
727                     this.matrix[2][3] = this.evalParam(11);
728                     this.matrix[3][0] = this.evalParam(12);
729                     this.matrix[3][1] = this.evalParam(13);
730                     this.matrix[3][2] = this.evalParam(14);
731                     this.matrix[3][3] = this.evalParam(15);
732                 };
733             } else if (type === 'matrix') {
734                 if (params.length !== 1) {
735                     throw new Error("JSXGraph: 3D transformation of type 'matrix' needs 1 parameter.");
736                 }
737 
738                 this.evalParam = params[0].slice();
739                 this.update = function () {
740                     var i, j;
741                     for (i = 0; i < 4; i++) {
742                         for (j = 0; j < 4; j++) {
743                             this.matrix[i][j] = Type.evaluate(this.evalParam[i][j]);
744                         }
745                     }
746                 };
747             }
748         },
749 
750         /**
751          * Transform a point element, that are: {@link Point}, {@link Text}, {@link Image}, {@link Point3D}.
752          * First, the transformation matrix is updated, then do the matrix-vector-multiplication.
753          * <p>
754          * Restricted to 2D transformations.
755          *
756          * @private
757          * @param {JXG.GeometryElement} p element which is transformed
758          * @param {String} 'self' Apply the transformation to the initialCoords instead of the coords if this is set.
759          * @returns {Array}
760          */
761         apply: function (p, self) {
762             var c;
763 
764             this.update();
765             if (this.is3D) {
766                 c = p.coords;
767             } else if (Type.exists(self)) {
768                 c = p.initialCoords.usrCoords;
769             } else {
770                 c = p.coords.usrCoords;
771             }
772 
773             return Mat.matVecMult(this.matrix, c);
774         },
775 
776         /**
777          * 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.
778          * If it is a free 2D point, then it can be dragged around later
779          * and will overwrite the transformed coordinates.
780          * @param {JXG.Point|Array} p
781          */
782         applyOnce: function (p) {
783             var c, len, i;
784 
785             if (!Type.isArray(p)) {
786                 p = [p];
787             }
788 
789             len = p.length;
790             for (i = 0; i < len; i++) {
791                 this.update();
792                 if (this.is3D) {
793                     p[i].coords = Mat.matVecMult(this.matrix, p[i].coords);
794                 } else {
795                     c = Mat.matVecMult(this.matrix, p[i].coords.usrCoords);
796                     p[i].coords.setCoordinates(Const.COORDS_BY_USER, c);
797                 }
798             }
799         },
800 
801         /**
802          * Binds a transformation to a GeometryElement or an array of elements. In every update of the
803          * GeometryElement(s), the transformation is executed. That means, in order to immediately
804          * apply the transformation after calling bindTo, a call of board.update() has to follow.
805          * <p>
806          * The transformation is simply appended to the existing list of transformations of the object.
807          * It is not fused (melt) with an existing transformation.
808          *
809          * @param  {Array|JXG.Object} el JXG.Object or array of JXG.Object to
810          *                            which the transformation is bound to.
811          * @see JXG.Transformation.meltTo
812          */
813         bindTo: function (el) {
814             var i, len;
815             if (Type.isArray(el)) {
816                 len = el.length;
817 
818                 for (i = 0; i < len; i++) {
819                     el[i].transformations.push(this);
820                 }
821             } else {
822                 el.transformations.push(this);
823             }
824         },
825 
826         /**
827          * Binds a transformation to a GeometryElement or an array of elements. In every update of the
828          * GeometryElement(s), the transformation is executed. That means, in order to immediately
829          * apply the transformation after calling meltTo, a call of board.update() has to follow.
830          * <p>
831          * In case the last transformation of the element and this transformation are static,
832          * i.e. the transformation matrices do not depend on other elements,
833          * the transformation will be fused into (multiplied with) the last transformation of
834          * the element. Thus, the list of transformations is kept small.
835          * If the transformation will be the first transformation ot the element, it will be cloned
836          * to prevent side effects.
837          *
838          * @param  {Array|JXG.Object} el JXG.Object or array of JXG.Objects to
839          *                            which the transformation is bound to.
840          *
841          * @see JXG.Transformation#bindTo
842          */
843         meltTo: function (el) {
844             var i, elt, t;
845 
846             if (Type.isArray(el)) {
847                 for (i = 0; i < el.length; i++) {
848                     this.meltTo(el[i]);
849                 }
850             } else {
851                 elt = el.transformations;
852                 if (elt.length > 0 &&
853                     elt[elt.length - 1].isNumericMatrix &&
854                     this.isNumericMatrix
855                 ) {
856                     elt[elt.length - 1].melt(this);
857                 } else {
858                     // Use a clone of the transformation.
859                     // Otherwise, if the transformation is meltTo twice
860                     // the transformation will be changed.
861                     t = this.clone();
862                     elt.push(t);
863                 }
864             }
865         },
866 
867         /**
868          * Create a copy of the transformation in case it is static, i.e.
869          * if the transformation matrix does not depend on other elements.
870          * <p>
871          * If the transformation matrix is not static, null will be returned.
872          *
873          * @returns {JXG.Transformation}
874          */
875         clone: function() {
876             var t = null;
877 
878             if (this.isNumericMatrix) {
879                 t = new JXG.Transformation(this.board, 'none', []);
880                 t.matrix = this.matrix.slice();
881             }
882 
883             return t;
884         },
885 
886         /**
887          * Unused
888          * @deprecated Use setAttribute
889          * @param term
890          */
891         setProperty: function (term) {
892             JXG.deprecated("Transformation.setProperty()", "Transformation.setAttribute()");
893         },
894 
895         /**
896          * Empty method. Unused.
897          * @param {Object} term Key-value pairs of the attributes.
898          */
899         setAttribute: function (term) {},
900 
901         /**
902          * Combine two transformations to one transformation. This only works if
903          * both of transformation matrices consist of numbers solely, and do not
904          * contain functions.
905          *
906          * Multiplies the transformation with a transformation t from the left.
907          * i.e. (this) = (t) join (this)
908          * @param  {JXG.Transform} t Transformation which is the left multiplicand
909          * @returns {JXG.Transform} the transformation object.
910          */
911         melt: function (t) {
912             var res = [];
913 
914             this.update();
915             t.update();
916 
917             res = Mat.matMatMult(t.matrix, this.matrix);
918 
919             this.update = function () {
920                 this.matrix = res;
921             };
922 
923             return this;
924         },
925 
926         // Documented in element.js
927         // Not yet, since transformations are not listed in board.objects.
928         getParents: function () {
929             var p = [[].concat.apply([], this.matrix)];
930 
931             if (this.parents.length !== 0) {
932                 p = this.parents;
933             }
934 
935             return p;
936         }
937     }
938 );
939 
940 /**
941  * @class Define projective 2D transformations like translation, rotation, reflection.
942  * @pseudo
943  * @description A transformation consists of a 3x3 matrix, i.e. it is a projective transformation.
944  * <p>
945  * Internally, a transformation is applied to an element by multiplying the 3x3 matrix from the left to
946  * the homogeneous coordinates of the element. JSXGraph represents homogeneous coordinates in the order
947  * (z, x, y). The matrix has the form
948  * <pre>
949  * ( a  b  c )   ( z )
950  * ( d  e  f ) * ( x )
951  * ( g  h  i )   ( y )
952  * </pre>
953  * where in general a=1. If b = c = 0, the transformation is called <i>affine</i>.
954  * In this case, finite points will stay finite. This is not the case for general projective coordinates.
955  * <p>
956  * Transformations acting on texts and images are considered to be affine, i.e. b and c are ignored.
957  *
958  * @name Transformation
959  * @augments JXG.Transformation
960  * @constructor
961  * @type JXG.Transformation
962  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
963  * @param {number|function|JXG.GeometryElement} parameters The parameters depend on the transformation type, supplied as attribute 'type'.
964  * Possible transformation types are
965  * <ul>
966  * <li> 'translate'
967  * <li> 'scale'
968  * <li> 'reflect'
969  * <li> 'rotate'
970  * <li> 'shear'
971  * <li> 'generic'
972  * <li> 'matrix'
973  * </ul>
974  * <p>Valid parameters for these types are:
975  * <dl>
976  * <dt><b><tt>type:"translate"</tt></b></dt><dd><b>x, y</b> Translation vector (two numbers or functions).
977  * The transformation matrix for x = a and y = b has the form:
978  * <pre>
979  * ( 1  0  0)   ( z )
980  * ( a  1  0) * ( x )
981  * ( b  0  1)   ( y )
982  * </pre>
983  * </dd>
984  * <dt><b><tt>type:"scale"</tt></b></dt><dd><b>scale_x, scale_y</b> Scale vector (two numbers or functions).
985  * The transformation matrix for scale_x = a and scale_y = b has the form:
986  * <pre>
987  * ( 1  0  0)   ( z )
988  * ( 0  a  0) * ( x )
989  * ( 0  0  b)   ( y )
990  * </pre>
991  * </dd>
992  * <dt><b><tt>type:"rotate"</tt></b></dt><dd> <b>alpha, [point | x, y]</b> The parameters are the angle value in Radians
993  *     (a number or function), and optionally a coordinate pair (two numbers or functions) or a point element defining the
994  *                rotation center. If the rotation center is not given, the transformation rotates around (0,0).
995  * The transformation matrix for angle a and rotating around (0, 0) has the form:
996  * <pre>
997  * ( 1    0        0      )   ( z )
998  * ( 0    cos(a)  -sin(a) ) * ( x )
999  * ( 0    sin(a)   cos(a) )   ( y )
1000  * </pre>
1001  * </dd>
1002  * <dt><b><tt>type:"shear"</tt></b></dt><dd><b>shear_x, shear_y</b> Shear vector (two numbers or functions).
1003  * The transformation matrix for shear_x = a and shear_y = b has the form:
1004  * <pre>
1005  * ( 1  0  0)   ( z )
1006  * ( 0  1  a) * ( x )
1007  * ( 0  b  1)   ( y )
1008  * </pre>
1009  * </dd>
1010  * <dt><b><tt>type:"reflect"</tt></b></dt><dd>The parameters can either be:
1011  *    <ul>
1012  *      <li> <b>line</b> a line element,
1013  *      <li> <b>p, q</b> two point elements,
1014  *      <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).
1015  *    </ul>
1016  * </dd>
1017  * <dt><b><tt>type:"affine"</tt></b></dt><dd><b>a, b, c, d</b> (numbers or functions>.
1018  * The transformation matrix has the form
1019  * <pre>
1020  * ( 1  0  0 )   ( z )
1021  * ( 0  a  b ) * ( x )
1022  * ( 0  c  d )   ( y )
1023  * </pre>
1024  * </dd>
1025  * <dt><b><tt>type:"affinematrix"</tt></b></dt><dd><b>M</b> 2x2 matrix containing numbers or functions.
1026  * The full transformation matrix has the form
1027  * <pre>
1028  * ( 1  0  0 )   ( z )
1029  * ( 0  M    ) * ( x )
1030  * ( 0       )   ( y )
1031  * </pre>
1032  * </dd>
1033  * <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)
1034  *  for a generic projective transformation.
1035  * The matrix has the form
1036  * <pre>
1037  * ( a  b  c )   ( z )
1038  * ( d  e  f ) * ( x )
1039  * ( g  h  i )   ( y )
1040  * </pre>
1041  * </dd>
1042  * <dt><b><tt>type:"matrix"</tt></b></dt><dd><b>M</b> 3x3 transformation matrix containing numbers or functions</dd>
1043  * </dl>
1044  *
1045  *
1046  * @see JXG.Transformation#setMatrix
1047  *
1048  * @example
1049  * // The point B is determined by taking twice the vector A from the origin
1050  *
1051  * var p0 = board.create('point', [0, 3], {name: 'A'}),
1052  *     t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type: 'translate'}),
1053  *     p1 = board.create('point', [p0, t], {color: 'blue'});
1054  *
1055  * </pre><div class="jxgbox" id="JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
1056  * <script type="text/javascript">
1057  *     (function() {
1058  *         var board = JXG.JSXGraph.initBoard('JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723',
1059  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1060  *     var p0 = board.create('point', [0, 3], {name: 'A'}),
1061  *         t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type:'translate'}),
1062  *         p1 = board.create('point', [p0, t], {color: 'blue'});
1063  *
1064  *     })();
1065  *
1066  * </script><pre>
1067  *
1068  * @example
1069  * // The point B is the result of scaling the point A with factor 2 in horizontal direction
1070  * // and with factor 0.5 in vertical direction.
1071  *
1072  * var p1 = board.create('point', [1, 1]),
1073  *     t = board.create('transform', [2, 0.5], {type: 'scale'}),
1074  *     p2 = board.create('point', [p1, t], {color: 'blue'});
1075  *
1076  * </pre><div class="jxgbox" id="JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
1077  * <script type="text/javascript">
1078  *     (function() {
1079  *         var board = JXG.JSXGraph.initBoard('JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723',
1080  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1081  *     var p1 = board.create('point', [1, 1]),
1082  *         t = board.create('transform', [2, 0.5], {type: 'scale'}),
1083  *         p2 = board.create('point', [p1, t], {color: 'blue'});
1084  *
1085  *     })();
1086  *
1087  * </script><pre>
1088  *
1089  * @example
1090  * // The point B is rotated around C which gives point D. The angle is determined
1091  * // by the vertical height of point A.
1092  *
1093  * var p0 = board.create('point', [0, 3], {name: 'A'}),
1094  *     p1 = board.create('point', [1, 1]),
1095  *     p2 = board.create('point', [2, 1], {name:'C', fixed: true}),
1096  *
1097  *     // angle, rotation center:
1098  *     t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}),
1099  *     p3 = board.create('point', [p1, t], {color: 'blue'});
1100  *
1101  * </pre><div class="jxgbox" id="JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
1102  * <script type="text/javascript">
1103  *     (function() {
1104  *         var board = JXG.JSXGraph.initBoard('JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723',
1105  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1106  *     var p0 = board.create('point', [0, 3], {name: 'A'}),
1107  *         p1 = board.create('point', [1, 1]),
1108  *         p2 = board.create('point', [2, 1], {name:'C', fixed: true}),
1109  *
1110  *         // angle, rotation center:
1111  *         t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}),
1112  *         p3 = board.create('point', [p1, t], {color: 'blue'});
1113  *
1114  *     })();
1115  *
1116  * </script><pre>
1117  *
1118  * @example
1119  * // A concatenation of several transformations.
1120  * var p1 = board.create('point', [1, 1]),
1121  *     t1 = board.create('transform', [-2, -1], {type: 'translate'}),
1122  *     t2 = board.create('transform', [Math.PI/4], {type: 'rotate'}),
1123  *     t3 = board.create('transform', [2, 1], {type: 'translate'}),
1124  *     p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'});
1125  *
1126  * </pre><div class="jxgbox" id="JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
1127  * <script type="text/javascript">
1128  *     (function() {
1129  *         var board = JXG.JSXGraph.initBoard('JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723',
1130  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1131  *     var p1 = board.create('point', [1, 1]),
1132  *         t1 = board.create('transform', [-2, -1], {type:'translate'}),
1133  *         t2 = board.create('transform', [Math.PI/4], {type:'rotate'}),
1134  *         t3 = board.create('transform', [2, 1], {type:'translate'}),
1135  *         p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'});
1136  *
1137  *     })();
1138  *
1139  * </script><pre>
1140  *
1141  * @example
1142  * // Reflection of point A
1143  * var p1 = board.create('point', [1, 1]),
1144  *     p2 = board.create('point', [1, 3]),
1145  *     p3 = board.create('point', [-2, 0]),
1146  *     l = board.create('line', [p2, p3]),
1147  *     t = board.create('transform', [l], {type: 'reflect'}),  // Possible are l, l.id, l.name
1148  *     p4 = board.create('point', [p1, t], {color: 'blue'});
1149  *
1150  * </pre><div class="jxgbox" id="JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
1151  * <script type="text/javascript">
1152  *     (function() {
1153  *         var board = JXG.JSXGraph.initBoard('JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723',
1154  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1155  *     var p1 = board.create('point', [1, 1]),
1156  *         p2 = board.create('point', [1, 3]),
1157  *         p3 = board.create('point', [-2, 0]),
1158  *         l = board.create('line', [p2, p3]),
1159  *         t = board.create('transform', [l], {type:'reflect'}),  // Possible are l, l.id, l.name
1160  *         p4 = board.create('point', [p1, t], {color: 'blue'});
1161  *
1162  *     })();
1163  *
1164  * </script><pre>
1165  *
1166  * @example
1167  * // Type: 'matrix'
1168  *         var y = board.create('slider', [[-3, 1], [-3, 4], [0, 1, 6]]);
1169  *         var t1 = board.create('transform', [
1170  *             [
1171  *                 [1, 0, 0],
1172  *                 [0, 1, 0],
1173  *                 [() => y.Value(), 0, 1]
1174  *             ]
1175  *         ], {type: 'matrix'});
1176  *
1177  *         var A = board.create('point', [2, -3]);
1178  *         var B = board.create('point', [A, t1]);
1179  *
1180  * </pre><div id="JXGd2bfd46c-3c0c-45c5-a92b-583fad0eb3ec" class="jxgbox" style="width: 300px; height: 300px;"></div>
1181  * <script type="text/javascript">
1182  *     (function() {
1183  *         var board = JXG.JSXGraph.initBoard('JXGd2bfd46c-3c0c-45c5-a92b-583fad0eb3ec',
1184  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1185  *             var y = board.create('slider', [[-3, 1], [-3, 4], [0, 1, 6]]);
1186  *             var t1 = board.create('transform', [
1187  *                 [
1188  *                     [1, 0, 0],
1189  *                     [0, 1, 0],
1190  *                     [() => y.Value(), 0, 1]
1191  *                 ]
1192  *             ], {type: 'matrix'});
1193  *
1194  *             var A = board.create('point', [2, -3]);
1195  *             var B = board.create('point', [A, t1]);
1196  *
1197  *     })();
1198  *
1199  * </script><pre>
1200  *
1201  * @example
1202  * // One time application of a transform to points A, B
1203  * var p1 = board.create('point', [1, 1]),
1204  *     p2 = board.create('point', [-1, -2]),
1205  *     t = board.create('transform', [3, 2], {type: 'shear'});
1206  * t.applyOnce([p1, p2]);
1207  *
1208  * </pre><div class="jxgbox" id="JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
1209  * <script type="text/javascript">
1210  *     (function() {
1211  *         var board = JXG.JSXGraph.initBoard('JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723',
1212  *             {boundingbox: [-8, 8, 8, -8], axis: true, showcopyright: false, shownavigation: false});
1213  *     var p1 = board.create('point', [1, 1]),
1214  *         p2 = board.create('point', [-1, -2]),
1215  *         t = board.create('transform', [3, 2], {type: 'shear'});
1216  *     t.applyOnce([p1, p2]);
1217  *
1218  *     })();
1219  *
1220  * </script><pre>
1221  *
1222  * @example
1223  * // Construct a square of side length 2 with the
1224  * // help of transformations
1225  *     var sq = [],
1226  *         right = board.create('transform', [2, 0], {type: 'translate'}),
1227  *         up = board.create('transform', [0, 2], {type: 'translate'}),
1228  *         pol, rot, p0;
1229  *
1230  *     // The first point is free
1231  *     sq[0] = board.create('point', [0, 0], {name: 'Drag me'}),
1232  *
1233  *     // Construct the other free points by transformations
1234  *     sq[1] = board.create('point', [sq[0], right]),
1235  *     sq[2] = board.create('point', [sq[0], [right, up]]),
1236  *     sq[3] = board.create('point', [sq[0], up]),
1237  *
1238  *     // Polygon through these four points
1239  *     pol = board.create('polygon', sq, {
1240  *             fillColor:'blue',
1241  *             gradient:'radial',
1242  *             gradientsecondcolor:'white',
1243  *             gradientSecondOpacity:'0'
1244  *     }),
1245  *
1246  *     p0 = board.create('point', [0, 3], {name: 'angle'}),
1247  *     // Rotate the square around point sq[0] by dragging A vertically.
1248  *     rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'});
1249  *
1250  *     // Apply the rotation to all but the first point of the square
1251  *     rot.bindTo(sq.slice(1));
1252  *
1253  * </pre><div class="jxgbox" id="JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
1254  * <script type="text/javascript">
1255  *     (function() {
1256  *         var board = JXG.JSXGraph.initBoard('JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723',
1257  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1258  *     // Construct a square of side length 2 with the
1259  *     // help of transformations
1260  *     var sq = [],
1261  *         right = board.create('transform', [2, 0], {type: 'translate'}),
1262  *         up = board.create('transform', [0, 2], {type: 'translate'}),
1263  *         pol, rot, p0;
1264  *
1265  *     // The first point is free
1266  *     sq[0] = board.create('point', [0, 0], {name: 'Drag me'}),
1267  *
1268  *     // Construct the other free points by transformations
1269  *     sq[1] = board.create('point', [sq[0], right]),
1270  *     sq[2] = board.create('point', [sq[0], [right, up]]),
1271  *     sq[3] = board.create('point', [sq[0], up]),
1272  *
1273  *     // Polygon through these four points
1274  *     pol = board.create('polygon', sq, {
1275  *             fillColor:'blue',
1276  *             gradient:'radial',
1277  *             gradientsecondcolor:'white',
1278  *             gradientSecondOpacity:'0'
1279  *     }),
1280  *
1281  *     p0 = board.create('point', [0, 3], {name: 'angle'}),
1282  *     // Rotate the square around point sq[0] by dragging A vertically.
1283  *     rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'});
1284  *
1285  *     // Apply the rotation to all but the first point of the square
1286  *     rot.bindTo(sq.slice(1));
1287  *
1288  *     })();
1289  *
1290  * </script><pre>
1291  *
1292  * @example
1293  * // Text transformation
1294  * var p0 = board.create('point', [0, 0], {name: 'p_0'});
1295  * var p1 = board.create('point', [3, 0], {name: 'p_1'});
1296  * var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'});
1297  *
1298  * // If p_0 is dragged, translate p_1 and text accordingly
1299  * var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'});
1300  * tOff.bindTo(txt);
1301  * tOff.bindTo(p1);
1302  *
1303  * // Rotate text around p_0 by dragging point p_1
1304  * var tRot = board.create('transform', [
1305  *     () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'});
1306  * tRot.bindTo(txt);
1307  *
1308  * // Scale text by dragging point "p_1"
1309  * // We do this by
1310  * // - moving text by -p_0 (inverse of transformation tOff),
1311  * // - scale the text (because scaling is relative to (0,0))
1312  * // - move the text back by +p_0
1313  * var tOffInv = board.create('transform', [
1314  *         () => -p0.X(),
1315  *         () => -p0.Y()
1316  * ], {type:'translate'});
1317  * var tScale = board.create('transform', [
1318  *         // Some scaling factor
1319  *         () => p1.Dist(p0) / 3,
1320  *         () => p1.Dist(p0) / 3
1321  * ], {type:'scale'});
1322  * tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt);
1323  *
1324  * </pre><div id="JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66" class="jxgbox" style="width: 300px; height: 300px;"></div>
1325  * <script type="text/javascript">
1326  *     (function() {
1327  *         var board = JXG.JSXGraph.initBoard('JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66',
1328  *             {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false});
1329  *     var p0 = board.create('point', [0, 0], {name: 'p_0'});
1330  *     var p1 = board.create('point', [3, 0], {name: 'p_1'});
1331  *     var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'});
1332  *
1333  *     // If p_0 is dragged, translate p_1 and text accordingly
1334  *     var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'});
1335  *     tOff.bindTo(txt);
1336  *     tOff.bindTo(p1);
1337  *
1338  *     // Rotate text around p_0 by dragging point p_1
1339  *     var tRot = board.create('transform', [
1340  *         () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'});
1341  *     tRot.bindTo(txt);
1342  *
1343  *     // Scale text by dragging point "p_1"
1344  *     // We do this by
1345  *     // - moving text by -p_0 (inverse of transformation tOff),
1346  *     // - scale the text (because scaling is relative to (0,0))
1347  *     // - move the text back by +p_0
1348  *     var tOffInv = board.create('transform', [
1349  *             () => -p0.X(),
1350  *             () => -p0.Y()
1351  *     ], {type:'translate'});
1352  *     var tScale = board.create('transform', [
1353  *             // Some scaling factor
1354  *             () => p1.Dist(p0) / 3,
1355  *             () => p1.Dist(p0) / 3
1356  *     ], {type:'scale'});
1357  *     tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt);
1358  *
1359  *     })();
1360  *
1361  * </script><pre>
1362  *
1363  */
1364 JXG.createTransform = function (board, parents, attributes) {
1365     return new JXG.Transformation(board, attributes.type, parents);
1366 };
1367 
1368 JXG.registerElement('transform', JXG.createTransform);
1369 
1370 /**
1371  * @class Define projective 3D transformations like translation, rotation, reflection.
1372  * @pseudo
1373  * @description A transformation consists of a 4x4 matrix, i.e. it is a projective transformation.
1374  * <p>
1375  * Internally, a transformation is applied to an element by multiplying the 4x4 matrix from the left to
1376  * the homogeneous coordinates of the element. JSXGraph represents homogeneous coordinates in the order
1377  * (w, x, y, z). If the coordinate is a finite point, w=1. The matrix has the form
1378  * <pre>
1379  * ( a b c d)   ( w )
1380  * ( e f g h) * ( x )
1381  * ( i j k l)   ( y )
1382  * ( m n o p)   ( z )
1383  * </pre>
1384  * where in general a=1. If b = c = d = 0, the transformation is called <i>affine</i>.
1385  * In this case, finite points will stay finite. This is not the case for general projective coordinates.
1386  * <p>
1387  *
1388  * @name Transformation3D
1389  * @augments JXG.Transformation
1390  * @constructor
1391  * @type JXG.Transformation
1392  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
1393  * @param {number|function|JXG.GeometryElement3D} parameters The parameters depend on the transformation type, supplied as attribute 'type'.
1394  *  Possible transformation types are
1395  * <ul>
1396  * <li> 'translate'
1397  * <li> 'scale'
1398  * <li> 'rotate'
1399  * <li> 'rotateX'
1400  * <li> 'rotateY'
1401  * <li> 'rotateZ'
1402  * <li> 'affine'
1403  * <li> 'affinematrix'
1404  * <li> 'generic'
1405  * <li> 'matrix'
1406  * </ul>
1407  * <p>Valid parameters for these types are:
1408  * <dl>
1409  * <dt><b><tt>type:"translate"</tt></b></dt><dd><b>x, y, z</b> Translation vector (three numbers or functions).
1410  * The transformation matrix for x = a, y = b, and z = c has the form:
1411  * <pre>
1412  * ( 1  0  0  0)   ( w )
1413  * ( a  1  0  0) * ( x )
1414  * ( b  0  1  0)   ( y )
1415  * ( c  0  0  c)   ( z )
1416  * </pre>
1417  * </dd>
1418  * <dt><b><tt>type:"scale"</tt></b></dt><dd><b>scale_x, scale_y, scale_z</b> Scale vector (three numbers or functions).
1419  * The transformation matrix for scale_x = a, scale_y = b, scale_z = c has the form:
1420  * <pre>
1421  * ( 1  0  0  0)   ( w )
1422  * ( 0  a  0  0) * ( x )
1423  * ( 0  0  b  0)   ( y )
1424  * ( 0  0  0  c)   ( z )
1425  * </pre>
1426  * </dd>
1427  * <dt><b><tt>type:"rotate"</tt></b></dt><dd><b>a, n, [p=[0,0,0]]</b> angle (in radians), normal, [point].
1428  * Rotate with angle a around the normal vector n through the point p.
1429  * </dd>
1430  * <dt><b><tt>type:"rotateX"</tt></b></dt><dd><b>a, [p=[0,0,0]]</b> angle (in radians), [point].
1431  * Rotate with angle a around the normal vector (1, 0, 0) through the point p.
1432  * </dd>
1433  * <dt><b><tt>type:"rotateY"</tt></b></dt><dd><b>a, [p=[0,0,0]]</b> angle (in radians), [point].
1434  * Rotate with angle a around the normal vector (0, 1, 0) through the point p.
1435  * </dd>
1436  * <dt><b><tt>type:"rotateZ"</tt></b></dt><dd><b>a, [p=[0,0,0]]</b> angle (in radians), [point].
1437  * Rotate with angle a around the normal vector (0, 0, 1) through the point p.
1438  * </dd>
1439  * <dt><b><tt>type:"affine"</tt></b></dt><dd><b>a,b,...,i</b> generic affine transformation (9 parameters, numbers or functions).
1440  * The full transformation matrix has the form
1441  * <pre>
1442  * ( 1  0  0  0 )   ( w )
1443  * ( 0  a  b  c ) * ( x )
1444  * ( 0  d  e  f )   ( y )
1445  * ( 0  g  h  i )   ( z )
1446  * </pre>
1447  * </dd>
1448  * <dt><b><tt>type:"affinematrix"</tt></b></dt><dd><b>M</b> generic affine 3x3 transformation matrix (containing numbers or functions).
1449  * The full transformation matrix has the form
1450  * <pre>
1451  * ( 1  0  0  0 )   ( w )
1452  * ( 0          ) * ( x )
1453  * ( 0     M    )   ( y )
1454  * ( 0          )   ( z )
1455  * </pre>
1456  * </dd>
1457  * <dt><b><tt>type:"generic"</tt></b></dt><dd><b>a,b,...,p</b> generic transformation (16 parameters, numbers or functions).
1458  * The full transformation matrix has the form
1459  * <pre>
1460  * ( a  b  c  d )   ( w )
1461  * ( e  f  ...  ) * ( x )
1462  * (    ...     )   ( y )
1463  * (    ...   p )   ( z )
1464  * </pre>
1465  * </dd>
1466  * <dt><b><tt>type:"matrix"</tt></b></dt><dd><b>M</b> generic 4x4 transformation matrix (containing numbers or functions).
1467  * The full transformation matrix has the form
1468  * <pre>
1469  * (            )   ( w )
1470  * (     M      ) * ( x )
1471  * (            )   ( y )
1472  * (            )   ( z )
1473  * </pre>
1474  * </dd>
1475  * </dl>
1476  *
1477  * @example
1478  * var bound = [-5, 5];
1479  * var view = board.create('view3d',
1480  *     [
1481  *         [-5, -5], [8, 8],
1482  *         [bound, bound, bound]
1483  *     ], {
1484  *         projection: "central",
1485  *         depthOrder: { enabled: true },
1486  *         axesPosition: 'border' // 'center', 'none'
1487  *     }
1488  * );
1489  *
1490  * var slider = board.create('slider', [[-4, 6], [0, 6], [0, 0, 5]]);
1491  *
1492  * var p1 = view.create('point3d', [1, 2, 2], { name: 'drag me', size: 5 });
1493  *
1494  * // Translate from p1 by fixed amount
1495  * var t1 = view.create('transform3d', [2, 3, 2], { type: 'translate' });
1496  * // Translate from p1 by dynamic amount
1497  * var t2 = view.create('transform3d', [() => slider.Value() + 3, 0, 0], { type: 'translate' });
1498  *
1499  * view.create('point3d', [p1, t1], { name: 'translate fixed', size: 5 });
1500  * view.create('point3d', [p1, t2], { name: 'translate by func', size: 5 });
1501  *
1502  * </pre><div id="JXG2409bb0a-90d7-4c1e-ae9f-85e8a776acec" class="jxgbox" style="width: 300px; height: 300px;"></div>
1503  * <script type="text/javascript">
1504  *     (function() {
1505  *         var board = JXG.JSXGraph.initBoard('JXG2409bb0a-90d7-4c1e-ae9f-85e8a776acec',
1506  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
1507  *     var bound = [-5, 5];
1508  *     var view = board.create('view3d',
1509  *         [
1510  *             [-5, -5], [8, 8],
1511  *             [bound, bound, bound]
1512  *         ], {
1513  *             projection: "central",
1514  *             depthOrder: { enabled: true },
1515  *             axesPosition: 'border' // 'center', 'none'
1516  *         }
1517  *     );
1518  *
1519  *     var slider = board.create('slider', [[-4, 6], [0, 6], [0, 0, 5]]);
1520  *
1521  *     var p1 = view.create('point3d', [1, 2, 2], { name: 'drag me', size: 5 });
1522  *
1523  *     // Translate from p1 by fixed amount
1524  *     var t1 = view.create('transform3d', [2, 3, 2], { type: 'translate' });
1525  *     // Translate from p1 by dynamic amount
1526  *     var t2 = view.create('transform3d', [() => slider.Value() + 3, 0, 0], { type: 'translate' });
1527  *
1528  *     view.create('point3d', [p1, t1], { name: 'translate fixed', size: 5 });
1529  *     view.create('point3d', [p1, t2], { name: 'translate by func', size: 5 });
1530  *
1531  *     })();
1532  *
1533  * </script><pre>
1534  *
1535  */
1536 JXG.createTransform3D = function (board, parents, attributes) {
1537     return new JXG.Transformation(board, attributes.type, parents, true);
1538 };
1539 
1540 JXG.registerElement('transform3d', JXG.createTransform3D);
1541 
1542 export default JXG.Transformation;
1543 
1544