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                 this.evalParam = Type.createEvalFunction(board, params, 2);
119                 this.update = function () {
120                     this.matrix[1][0] = this.evalParam(0);
121                     this.matrix[2][0] = this.evalParam(1);
122                 };
123             } else if (type === 'scale') {
124                 this.evalParam = Type.createEvalFunction(board, params, 2);
125                 this.update = function () {
126                     this.matrix[1][1] = this.evalParam(0); // x
127                     this.matrix[2][2] = this.evalParam(1); // y
128                 };
129             // Input: line or two points
130             } else if (type === 'reflect') {
131                 // line or two points
132                 if (params.length < 4) {
133                     params[0] = board.select(params[0]);
134                 }
135 
136                 // two points
137                 if (params.length === 2) {
138                     params[1] = board.select(params[1]);
139                 }
140 
141                 // 4 coordinates [px,py,qx,qy]
142                 if (params.length === 4) {
143                     this.evalParam = Type.createEvalFunction(board, params, 4);
144                 }
145 
146                 this.update = function () {
147                     var x, y, z, xoff, yoff, d,
148                         v, p;
149                     // Determine homogeneous coordinates of reflections axis
150                     // line
151                     if (params.length === 1) {
152                         v = params[0].stdform;
153                     // two points
154                     } else if (params.length === 2) {
155                         v = Mat.crossProduct(params[1].coords.usrCoords, params[0].coords.usrCoords);
156                     // two points coordinates [px,py,qx,qy]
157                     } else if (params.length === 4) {
158                         v = Mat.crossProduct(
159                             [1, this.evalParam(2), this.evalParam(3)],
160                             [1, this.evalParam(0), this.evalParam(1)]
161                         );
162                     }
163 
164                     // Project origin to the line.  This gives a finite point p
165                     x = v[1];
166                     y = v[2];
167                     z = v[0];
168                     p = [-z * x, -z * y, x * x + y * y];
169                     d = p[2];
170 
171                     // Normalize p
172                     xoff = p[0] / p[2];
173                     yoff = p[1] / p[2];
174 
175                     // x, y is the direction of the line
176                     x = -v[2];
177                     y =  v[1];
178 
179                     this.matrix[1][1] = (x * x - y * y) / d;
180                     this.matrix[1][2] = 2 * x * y / d;
181                     this.matrix[2][1] = this.matrix[1][2];
182                     this.matrix[2][2] = -this.matrix[1][1];
183                     this.matrix[1][0] = xoff * (1 - this.matrix[1][1]) - yoff * this.matrix[1][2];
184                     this.matrix[2][0] = yoff * (1 - this.matrix[2][2]) - xoff * this.matrix[2][1];
185                 };
186             } else if (type === 'rotate') {
187                 // angle, x, y
188                 if (params.length === 3) {
189                     this.evalParam = Type.createEvalFunction(board, params, 3);
190                 // angle, p or angle
191                 } else if (params.length <= 2) {
192                     this.evalParam = Type.createEvalFunction(board, params, 1);
193 
194                     if (params.length === 2) {
195                         params[1] = board.select(params[1]);
196                     }
197                 }
198 
199                 this.update = function () {
200                     var x, y,
201                         beta = this.evalParam(0),
202                         co = Math.cos(beta),
203                         si = Math.sin(beta);
204 
205                     this.matrix[1][1] =  co;
206                     this.matrix[1][2] = -si;
207                     this.matrix[2][1] =  si;
208                     this.matrix[2][2] =  co;
209 
210                     // rotate around [x,y] otherwise rotate around [0,0]
211                     if (params.length > 1) {
212                         if (params.length === 3) {
213                             x = this.evalParam(1);
214                             y = this.evalParam(2);
215                         } else {
216                             x = params[1].X();
217                             y = params[1].Y();
218                         }
219                         this.matrix[1][0] = x * (1 - co) + y * si;
220                         this.matrix[2][0] = y * (1 - co) - x * si;
221                     }
222                 };
223             } else if (type === 'shear') {
224                 this.evalParam = Type.createEvalFunction(board, params, 2);
225 
226                 this.update = function () {
227                     this.matrix[1][2] = this.evalParam(0);
228                     this.matrix[2][1] = this.evalParam(1);
229                 };
230             } else if (type === 'generic') {
231                 this.evalParam = Type.createEvalFunction(board, params, 9);
232 
233                 this.update = function () {
234                     this.matrix[0][0] = this.evalParam(0);
235                     this.matrix[0][1] = this.evalParam(1);
236                     this.matrix[0][2] = this.evalParam(2);
237                     this.matrix[1][0] = this.evalParam(3);
238                     this.matrix[1][1] = this.evalParam(4);
239                     this.matrix[1][2] = this.evalParam(5);
240                     this.matrix[2][0] = this.evalParam(6);
241                     this.matrix[2][1] = this.evalParam(7);
242                     this.matrix[2][2] = this.evalParam(8);
243                 };
244             }
245         },
246 
247         /**
248          * Transform a GeometryElement:
249          * Update the matrix first, then do the matrix-vector-multiplication
250          * @param {JXG.GeometryElement} p element which is transformed
251          * @param {String} self Apply the transformation to the initialCoords instead of the coords if this is set.
252          * @returns {Array}
253          */
254         apply: function (p, self) {
255             this.update();
256 
257             if (Type.exists(self)) {
258                 return Mat.matVecMult(this.matrix, p.initialCoords.usrCoords);
259             }
260             return Mat.matVecMult(this.matrix, p.coords.usrCoords);
261         },
262 
263         /**
264          * Apply a transformation once to a GeometryElement.
265          * If it is a free point, then it can be dragged around later
266          * and will overwrite the transformed coordinates.
267          * @param {JXG.Point,Array} p
268          */
269         applyOnce: function (p) {
270             var c, len, i;
271 
272             if (!Type.isArray(p)) {
273                 p = [p];
274             }
275 
276             len = p.length;
277 
278             for (i = 0; i < len; i++) {
279                 this.update();
280                 c = Mat.matVecMult(this.matrix, p[i].coords.usrCoords);
281                 p[i].coords.setCoordinates(Const.COORDS_BY_USER, c);
282             }
283         },
284 
285         /**
286          * Bind a transformation to a GeometryElement
287          */
288         bindTo: function (p) {
289             var i, len;
290             if (Type.isArray(p)) {
291                 len = p.length;
292 
293                 for (i = 0; i < len; i++) {
294                     p[i].transformations.push(this);
295                 }
296             } else {
297                 p.transformations.push(this);
298             }
299         },
300 
301         /**
302          * @deprecated Use setAttribute
303          * @param term
304          */
305         setProperty: function (term) { },
306 
307         setAttribute: function (term) { },
308 
309         /**
310          * Multiplication of a transformation t from the right.
311          * this = t join this
312          */
313         melt: function (t) {
314             var res = [], i, len, len0, k, s, j;
315 
316             len = t.matrix.length;
317             len0 = this.matrix[0].length;
318 
319             for (i = 0; i < len; i++) {
320                 res[i] = [];
321             }
322 
323             this.update();
324             t.update();
325 
326             for (i = 0; i < len; i++) {
327                 for (j = 0; j < len0; j++) {
328                     s = 0;
329                     for (k = 0; k < len; k++) {
330                         s += t.matrix[i][k] * this.matrix[k][j];
331                     }
332                     res[i][j] = s;
333                 }
334             }
335 
336             this.update = function () {
337                 var len = this.matrix.length,
338                     len0 = this.matrix[0].length;
339 
340                 for (i = 0; i < len; i++) {
341                     for (j = 0; j < len0; j++) {
342                         this.matrix[i][j] = res[i][j];
343                     }
344                 }
345             };
346             return this;
347         }
348     });
349 
350     JXG.createTransform = function (board, parents, attributes) {
351         return new JXG.Transformation(board, attributes.type, parents);
352     };
353 
354     JXG.registerElement('transform', JXG.createTransform);
355 
356     return {
357         Transformation: JXG.Transformation,
358         createTransform: JXG.createTransform
359     };
360 });