      1 /*
      2     Copyright 2008-2013
      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 <http://www.gnu.org/licenses/>
     29     and <http://opensource.org/licenses/MIT/>.
     30  */
     31
     32
     33 /*global JXG: true, define: true*/
     34 /*jslint nomen: true, plusplus: true*/
     35
     36 /*depends:
     37  jxg
     38  base/constants
     39  math/math
     40  utils/type
     41  */
     42
     43 /**
     44  * @fileoverview This file contains code for transformations of geometrical objects.
     45  * @author graphjs
     46  * @version 0.1
     47  */
     48
     49 define([
     50     'jxg', 'base/constants', 'math/math', 'utils/type'
     51 ], function (JXG, Const, Mat, Type) {
     52
     53     "use strict";
     54
     55     /**
     56      * Possible types:
     57      * - translate
     58      * - scale
     59      * - reflect
     60      * - rotate
     61      * - shear
     62      * - generic
     63      *
     64      * Rotation matrix:
     65      * ( 1    0           0   )
     66      * ( 0    cos(a)   -sin(a))
     67      * ( 0    sin(a)   cos(a) )
     68      *
     69      * Translation matrix:
     70      * ( 1  0  0)
     71      * ( a  1  0)
     72      * ( b  0  1)
     73      */
     74     JXG.Transformation = function (board, type, params) {
     75         this.elementClass = Const.OBJECT_CLASS_OTHER;
     76         this.matrix = [
     77             [1, 0, 0],
     78             [0, 1, 0],
     79             [0, 0, 1]
     80         ];
     81         this.board = board;
     82         this.isNumericMatrix = false;
     83         this.setMatrix(board, type, params);
     84
     85         this.methodMap = {
     86             apply: 'apply',
     87             applyOnce: 'applyOnce',
     88             bindTo: 'bindTo',
     89             bind: 'bind',
     90             melt: 'melt'
     91         };
     92     };
     93
     94     JXG.Transformation.prototype = {};
     95
     96     JXG.extend(JXG.Transformation.prototype, /** @lends JXG.Transformation.prototype */ {
     97         update: function () {
     98             return this;
     99         },
    100
    101         /**
    102          * Set the transformation matrix for different
    103          * types of standard transforms
    104          */
    105         setMatrix: function (board, type, params) {
    106             var i;
    107
    108             this.isNumericMatrix = true;
    109
    110             for (i = 0; i < params.length; i++) {
    111                 if (typeof params[i] !== 'number') {
    112                     this.isNumericMatrix = false;
    113                     break;
    114                 }
    115             }
    116
    117             if (type === 'translate') {
    118                 if (params.length !== 2) {
    119                     throw new Error("JSXGraph: translate transformation needs 2 parameters.");
    120                 }
    121                 this.evalParam = Type.createEvalFunction(board, params, 2);
    122                 this.update = function () {
    123                     this.matrix[1][0] = this.evalParam(0);
    124                     this.matrix[2][0] = this.evalParam(1);
    125                 };
    126             } else if (type === 'scale') {
    127                 if (params.length !== 2) {
    128                     throw new Error("JSXGraph: scale transformation needs 2 parameters.");
    129                 }
    130                 this.evalParam = Type.createEvalFunction(board, params, 2);
    131                 this.update = function () {
    132                     this.matrix[1][1] = this.evalParam(0); // x
    133                     this.matrix[2][2] = this.evalParam(1); // y
    134                 };
    135             // Input: line or two points
    136             } else if (type === 'reflect') {
    137                 // line or two points
    138                 if (params.length < 4) {
    139                     params[0] = board.select(params[0]);
    140                 }
    141
    142                 // two points
    143                 if (params.length === 2) {
    144                     params[1] = board.select(params[1]);
    145                 }
    146
    147                 // 4 coordinates [px,py,qx,qy]
    148                 if (params.length === 4) {
    149                     this.evalParam = Type.createEvalFunction(board, params, 4);
    150                 }
    151
    152                 this.update = function () {
    153                     var x, y, z, xoff, yoff, d,
    154                         v, p;
    155                     // Determine homogeneous coordinates of reflections axis
    156                     // line
    157                     if (params.length === 1) {
    158                         v = params[0].stdform;
    159                     // two points
    160                     } else if (params.length === 2) {
    161                         v = Mat.crossProduct(params[1].coords.usrCoords, params[0].coords.usrCoords);
    162                     // two points coordinates [px,py,qx,qy]
    163                     } else if (params.length === 4) {
    164                         v = Mat.crossProduct(
    165                             [1, this.evalParam(2), this.evalParam(3)],
    166                             [1, this.evalParam(0), this.evalParam(1)]
    167                         );
    168                     }
    169
    170                     // Project origin to the line.  This gives a finite point p
    171                     x = v[1];
    172                     y = v[2];
    173                     z = v[0];
    174                     p = [-z * x, -z * y, x * x + y * y];
    175                     d = p[2];
    176
    177                     // Normalize p
    178                     xoff = p[0] / p[2];
    179                     yoff = p[1] / p[2];
    180
    181                     // x, y is the direction of the line
    182                     x = -v[2];
    183                     y =  v[1];
    184
    185                     this.matrix[1][1] = (x * x - y * y) / d;
    186                     this.matrix[1][2] = 2 * x * y / d;
    187                     this.matrix[2][1] = this.matrix[1][2];
    188                     this.matrix[2][2] = -this.matrix[1][1];
    189                     this.matrix[1][0] = xoff * (1 - this.matrix[1][1]) - yoff * this.matrix[1][2];
    190                     this.matrix[2][0] = yoff * (1 - this.matrix[2][2]) - xoff * this.matrix[2][1];
    191                 };
    192             } else if (type === 'rotate') {
    193                 // angle, x, y
    194                 if (params.length === 3) {
    195                     this.evalParam = Type.createEvalFunction(board, params, 3);
    196                 // angle, p or angle
    197                 } else if (params.length > 0 && params.length <= 2) {
    198                     this.evalParam = Type.createEvalFunction(board, params, 1);
    199
    200                     if (params.length === 2) {
    201                         params[1] = board.select(params[1]);
    202                     }
    203                 }
    204
    205                 this.update = function () {
    206                     var x, y,
    207                         beta = this.evalParam(0),
    208                         co = Math.cos(beta),
    209                         si = Math.sin(beta);
    210
    211                     this.matrix[1][1] =  co;
    212                     this.matrix[1][2] = -si;
    213                     this.matrix[2][1] =  si;
    214                     this.matrix[2][2] =  co;
    215
    216                     // rotate around [x,y] otherwise rotate around [0,0]
    217                     if (params.length > 1) {
    218                         if (params.length === 3) {
    219                             x = this.evalParam(1);
    220                             y = this.evalParam(2);
    221                         } else {
    222                             x = params[1].X();
    223                             y = params[1].Y();
    224                         }
    225                         this.matrix[1][0] = x * (1 - co) + y * si;
    226                         this.matrix[2][0] = y * (1 - co) - x * si;
    227                     }
    228                 };
    229             } else if (type === 'shear') {
    230                 if (params.length !== 2) {
    231                     throw new Error("JSXGraph: shear transformation needs 2 parameters.");
    232                 }
    233
    234                 this.evalParam = Type.createEvalFunction(board, params, 2);
    235                 this.update = function () {
    236                     this.matrix[1][2] = this.evalParam(0);
    237                     this.matrix[2][1] = this.evalParam(1);
    238                 };
    239             } else if (type === 'generic') {
    240                 if (params.length !== 9) {
    241                     throw new Error("JSXGraph: generic transformation needs 9 parameters.");
    242                 }
    243
    244                 this.evalParam = Type.createEvalFunction(board, params, 9);
    245
    246                 this.update = function () {
    247                     this.matrix[0][0] = this.evalParam(0);
    248                     this.matrix[0][1] = this.evalParam(1);
    249                     this.matrix[0][2] = this.evalParam(2);
    250                     this.matrix[1][0] = this.evalParam(3);
    251                     this.matrix[1][1] = this.evalParam(4);
    252                     this.matrix[1][2] = this.evalParam(5);
    253                     this.matrix[2][0] = this.evalParam(6);
    254                     this.matrix[2][1] = this.evalParam(7);
    255                     this.matrix[2][2] = this.evalParam(8);
    256                 };
    257             }
    258         },
    259
    260         /**
    261          * Transform a GeometryElement:
    262          * Update the matrix first, then do the matrix-vector-multiplication
    263          * @param {JXG.GeometryElement} p element which is transformed
    264          * @param {String} self Apply the transformation to the initialCoords instead of the coords if this is set.
    265          * @returns {Array}
    266          */
    267         apply: function (p, self) {
    268             this.update();
    269
    270             if (Type.exists(self)) {
    271                 return Mat.matVecMult(this.matrix, p.initialCoords.usrCoords);
    272             }
    273             return Mat.matVecMult(this.matrix, p.coords.usrCoords);
    274         },
    275
    276         /**
    277          * Apply a transformation once to a GeometryElement.
    278          * If it is a free point, then it can be dragged around later
    279          * and will overwrite the transformed coordinates.
    280          * @param {JXG.Point,Array} p
    281          */
    282         applyOnce: function (p) {
    283             var c, len, i;
    284
    285             if (!Type.isArray(p)) {
    286                 p = [p];
    287             }
    288
    289             len = p.length;
    290
    291             for (i = 0; i < len; i++) {
    292                 this.update();
    293                 c = Mat.matVecMult(this.matrix, p[i].coords.usrCoords);
    294                 p[i].coords.setCoordinates(Const.COORDS_BY_USER, c);
    295             }
    296         },
    297
    298         /**
    299          * Bind a transformation to a GeometryElement
    300          */
    301         bindTo: function (p) {
    302             var i, len;
    303             if (Type.isArray(p)) {
    304                 len = p.length;
    305
    306                 for (i = 0; i < len; i++) {
    307                     p[i].transformations.push(this);
    308                 }
    309             } else {
    310                 p.transformations.push(this);
    311             }
    312         },
    313
    314         /**
    315          * @deprecated Use setAttribute
    316          * @param term
    317          */
    318         setProperty: function (term) { },
    319
    320         setAttribute: function (term) { },
    321
    322         /**
    323          * Multiplication of a transformation t from the right.
    324          * this = t join this
    325          */
    326         melt: function (t) {
    327             var res = [], i, len, len0, k, s, j;
    328
    329             len = t.matrix.length;
    330             len0 = this.matrix[0].length;
    331
    332             for (i = 0; i < len; i++) {
    333                 res[i] = [];
    334             }
    335
    336             this.update();
    337             t.update();
    338
    339             for (i = 0; i < len; i++) {
    340                 for (j = 0; j < len0; j++) {
    341                     s = 0;
    342                     for (k = 0; k < len; k++) {
    343                         s += t.matrix[i][k] * this.matrix[k][j];
    344                     }
    345                     res[i][j] = s;
    346                 }
    347             }
    348
    349             this.update = function () {
    350                 var len = this.matrix.length,
    351                     len0 = this.matrix[0].length;
    352
    353                 for (i = 0; i < len; i++) {
    354                     for (j = 0; j < len0; j++) {
    355                         this.matrix[i][j] = res[i][j];
    356                     }
    357                 }
    358             };
    359             return this;
    360         }
    361     });
    362
    363     JXG.createTransform = function (board, parents, attributes) {
    364         return new JXG.Transformation(board, attributes.type, parents);
    365     };
    366
    367     JXG.registerElement('transform', JXG.createTransform);
    368
    369     return {
    370         Transformation: JXG.Transformation,
    371         createTransform: JXG.createTransform
    372     };
    373 });
    374