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