      1 /*
      2     Copyright 2008,2009
      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: you can redistribute it and/or modify
     13     it under the terms of the GNU Lesser General Public License as published by
     14     the Free Software Foundation, either version 3 of the License, or
     15     (at your option) any later version.
     16
     17     JSXGraph is distributed in the hope that it will be useful,
     18     but WITHOUT ANY WARRANTY; without even the implied warranty of
     19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     20     GNU Lesser General Public License for more details.
     21
     22     You should have received a copy of the GNU Lesser General Public License
     23     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
     24 */
     25
     26 /**
     27  * @fileoverview This file contains code for transformations of geometrical objects.
     28  * @author graphjs
     29  * @version 0.1
     30  *
     31  * Possible types:
     32  * - translate
     33  * - scale
     34  * - reflect
     35  * - rotate
     36  * - shear
     37  * - generic
     38  *
     39  * Rotation matrix:
     40  * ( 1    0           0   )
     41  * ( 0    cos(a)   -sin(a))
     42  * ( 0    sin(a)   cos(a) )
     43  *
     44  * Translation matrix:
     45  * ( 1  0  0)
     46  * ( a  1  0)
     47  * ( b  0  1)
     48
     49  */
     50 JXG.Transformation = function(board,type, params) {
     51     this.elementClass = JXG.OBJECT_CLASS_OTHER;
     52     this.matrix = [[1,0,0],[0,1,0],[0,0,1]];
     53     this.board = board;
     54     this.isNumericMatrix = false;
     55     this.setMatrix(board,type,params);
     56 };
     57 JXG.Transformation.prototype = {};
     58
     59 JXG.extend(JXG.Transformation.prototype, /** @lends JXG.Transformation.prototype */ {
     60     update: function(){ return this;},
     61
     62     /**
     63      * Set the transformation matrix for different
     64      * types of standard transforms
     65      */
     66     setMatrix: function(board,type,params) {
     67         var i;
     68
     69         this.isNumericMatrix = true;
     70         for (i=0;i<params.length;i++) {
     71             if (typeof params[i]!='number') {
     72                 this.isNumericMatrix = false;
     73                 break;
     74             }
     75         }
     76
     77         if (type=='translate') {
     78             this.evalParam = JXG.createEvalFunction(board,params,2);
     79             this.update = function() {
     80                 this.matrix[1][0] = this.evalParam(0);
     81                 this.matrix[2][0] = this.evalParam(1);
     82             };
     83         } else if (type=='scale') {
     84             this.evalParam = JXG.createEvalFunction(board,params,2);
     85             this.update = function() {
     86                 this.matrix[1][1] = this.evalParam(0); // x
     87                 this.matrix[2][2] = this.evalParam(1); // y
     88             };
     89         } else if (type=='reflect') {  // Input: line or two points
     90             if (params.length<4) { // line or two points
     91                 params[0] = JXG.getReference(board,params[0]);
     92             }
     93             if (params.length==2) { // two points
     94                 params[1] = JXG.getReference(board,params[1]);
     95             }
     96             if (params.length==4) { // 4 coordinates [px,py,qx,qy]
     97                 this.evalParam = JXG.createEvalFunction(board,params,4);
     98             }
     99             this.update = function() {
    100                 var x, y, xoff, yoff, d;
    101
    102                 if (params.length==1) { // line
    103                     x = params[0].point2.X()-params[0].point1.X();
    104                     y = params[0].point2.Y()-params[0].point1.Y();
    105                     xoff = params[0].point1.X();
    106                     yoff = params[0].point1.Y();
    107                 } else if (params.length==2){ // two points
    108                     x = params[1].X()-params[0].X();
    109                     y = params[1].Y()-params[0].Y();
    110                     xoff = params[0].X();
    111                     yoff = params[0].Y();
    112                 } else if (params.length==4){ // two points coordinates [px,py,qx,qy]
    113                     x = this.evalParam(2)-this.evalParam(0);
    114                     y = this.evalParam(3)-this.evalParam(1);
    115                     xoff = this.evalParam(0);
    116                     yoff = this.evalParam(1);
    117                 }
    118                 d = x*x+y*y;
    119                 this.matrix[1][1] = (x*x-y*y)/d;
    120                 this.matrix[1][2] = 2*x*y/d;
    121                 this.matrix[2][1] = 2*x*y/d;
    122                 this.matrix[2][2] = (-x*x+y*y)/d;
    123                 this.matrix[1][0] = xoff*(1-this.matrix[1][1])-yoff*this.matrix[1][2];
    124                 this.matrix[2][0] = yoff*(1-this.matrix[2][2])-xoff*this.matrix[2][1];
    125             };
    126         } else if (type=='rotate') {
    127             if (params.length==3) { // angle, x, y
    128                 this.evalParam = JXG.createEvalFunction(board,params,3);
    129             } else if (params.length<=2) { // angle, p or angle
    130                 this.evalParam = JXG.createEvalFunction(board,params,1);
    131                 if (params.length==2) {
    132                     params[1] = JXG.getReference(board,params[1]);
    133                 }
    134             }
    135             this.update = function() {
    136                 var beta = this.evalParam(0), x, y, co = Math.cos(beta), si = Math.sin(beta);
    137                 this.matrix[1][1] =  co;
    138                 this.matrix[1][2] = -si;
    139                 this.matrix[2][1] =  si;
    140                 this.matrix[2][2] =  co;
    141                 if (params.length>1) {  // rotate around [x,y] otherwise rotate around [0,0]
    142                     if (params.length==3) {
    143                         x = this.evalParam(1);
    144                         y = this.evalParam(2);
    145                     } else {
    146                         x = params[1].X();
    147                         y = params[1].Y();
    148                     }
    149                     this.matrix[1][0] = x*(1-co)+y*si;
    150                     this.matrix[2][0] = y*(1-co)-x*si;
    151                 }
    152             };
    153         } else if (type=='shear') {
    154             this.evalParam = JXG.createEvalFunction(board,params,1);
    155             this.update = function() {
    156                 var beta = this.evalParam(0);
    157                 this.matrix[1][1] = Math.tan(beta);
    158             };
    159         } else if (type=='generic') {
    160             this.evalParam = JXG.createEvalFunction(board,params,9);
    161             this.update = function() {
    162                 this.matrix[0][0] = this.evalParam(0);
    163                 this.matrix[0][1] = this.evalParam(1);
    164                 this.matrix[0][2] = this.evalParam(2);
    165                 this.matrix[1][0] = this.evalParam(3);
    166                 this.matrix[1][1] = this.evalParam(4);
    167                 this.matrix[1][2] = this.evalParam(5);
    168                 this.matrix[2][0] = this.evalParam(6);
    169                 this.matrix[2][1] = this.evalParam(7);
    170                 this.matrix[2][2] = this.evalParam(8);
    171             };
    172         }
    173     },
    174
    175     /**
    176      * Transform a GeometryElement:
    177      * First, update the matrix
    178      * Second, do the matrix-vector-multiplication
    179      *
    180      * @param {JXG.GeometryElement} element, which is transformed
    181      */
    182     apply: function(p){
    183         this.update();
    184         if (arguments[1]!=null) {
    185             return JXG.Math.matVecMult(this.matrix,p.initialCoords.usrCoords);
    186         } else {
    187             return JXG.Math.matVecMult(this.matrix,p.coords.usrCoords);
    188         }
    189     },
    190
    191     /**
    192      * Apply a transformation once to a GeometryElement.
    193      * If it is a free point, then it can be dragged around later
    194      * and will overwrite the transformed coordinates.
    195      */
    196     applyOnce: function(p){
    197         var c, len, i;
    198         if (!JXG.isArray(p)) {
    199             this.update();
    200             c = JXG.Math.matVecMult(this.matrix,p.coords.usrCoords);
    201             p.coords.setCoordinates(JXG.COORDS_BY_USER, c);
    202         } else {
    203             len = p.length;
    204             for (i=0; i<len; i++) {
    205                 this.update();
    206                 c = JXG.Math.matVecMult(this.matrix,p[i].coords.usrCoords);
    207                 p[i].coords.setCoordinates(JXG.COORDS_BY_USER, c);
    208             }
    209         }
    210     },
    211
    212     /**
    213      * Bind a transformation to a GeometryElement
    214      */
    215     bindTo: function(p){
    216         var i, len;
    217         if (JXG.isArray(p)) {
    218             len = p.length;
    219             for (i=0; i<len; i++) {
    220                 p[i].transformations.push(this);
    221             }
    222         } else {
    223             p.transformations.push(this);
    224         }
    225     },
    226
    227     setProperty: function(term) {
    228     },
    229
    230     /**
    231      * Multiplication of a transformation t from the right.
    232      * this = t join this
    233      */
    234     melt: function(t){
    235         var res = [], i, len, len0, k, s, j;
    236
    237         len = t.matrix.length;
    238         len0 = this.matrix[0].length;
    239
    240         for (i=0;i<len;i++) {
    241             res[i] = [];
    242         }
    243         this.update();
    244         t.update();
    245         for (i=0;i<len;i++) {
    246             for (j=0;j<len0;j++) {
    247                 s = 0;
    248                 for (k=0;k<len;k++) {
    249                     s += t.matrix[i][k]*this.matrix[k][j];
    250                 }
    251                 res[i][j] = s;
    252             }
    253         }
    254         this.update = function() {
    255             var len = this.matrix.length,
    256                 len0 = this.matrix[0].length;
    257             for (i=0;i<len;i++) {
    258                 for (j=0;j<len0;j++) {
    259                     this.matrix[i][j] = res[i][j];
    260                 }
    261             }
    262         };
    263         return this;
    264     }
    265 });
    266
    267 JXG.createTransform = function(board, parents, attributes) {
    268     return new JXG.Transformation(board, attributes['type'], parents);
    269 };
    270
    271 JXG.JSXGraph.registerElement('transform', JXG.createTransform);
    272