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