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, z, xoff, yoff, d,
101                     v, p;
102                 // Determine homogeneous coordinates of reflections axis
103                 if (params.length==1) { // line
104                     v = params[0].stdform;
105                 } else if (params.length==2){ // two points
106                     v = JXG.Math.crossProduct(params[1].coords.usrCoords, params[0].coords.usrCoords);
107                 } else if (params.length==4){ // two points coordinates [px,py,qx,qy]
108                     v = JXG.Math.crossProduct(
109                         [1, this.evalParam(2), this.evalParam(3)],
110                         [1, this.evalParam(0), this.evalParam(1)]);
111                 }
112                 // Project origin to the line.  This gives a finite point p
113                 x = v[1];
114                 y = v[2];
115                 z = v[0];
116                 p = [-z*x, -z*y, x*x+y*y];
117                 d = p[2];
118                 // Normalize p
119                 xoff = p[0]/p[2];
120                 yoff = p[1]/p[2];
121                 // x, y is the direction of the line
122                 x = -v[2];
123                 y =  v[1];
124                 
125                 this.matrix[1][1] = (x*x-y*y)/d;
126                 this.matrix[1][2] = 2*x*y/d;
127                 this.matrix[2][1] = this.matrix[1][2];
128                 this.matrix[2][2] = -this.matrix[1][1];
129                 this.matrix[1][0] = xoff*(1-this.matrix[1][1])-yoff*this.matrix[1][2];
130                 this.matrix[2][0] = yoff*(1-this.matrix[2][2])-xoff*this.matrix[2][1];
131             };
132         } else if (type=='rotate') {
133             if (params.length==3) { // angle, x, y
134                 this.evalParam = JXG.createEvalFunction(board,params,3);
135             } else if (params.length<=2) { // angle, p or angle
136                 this.evalParam = JXG.createEvalFunction(board,params,1);
137                 if (params.length==2) {
138                     params[1] = JXG.getReference(board,params[1]);
139                 } 
140             }
141             this.update = function() {
142                 var beta = this.evalParam(0), x, y, co = Math.cos(beta), si = Math.sin(beta);
143                 this.matrix[1][1] =  co; 
144                 this.matrix[1][2] = -si;  
145                 this.matrix[2][1] =  si; 
146                 this.matrix[2][2] =  co; 
147                 if (params.length>1) {  // rotate around [x,y] otherwise rotate around [0,0]
148                     if (params.length==3) {
149                         x = this.evalParam(1);
150                         y = this.evalParam(2);
151                     } else {
152                         x = params[1].X();
153                         y = params[1].Y();
154                     }
155                     this.matrix[1][0] = x*(1-co)+y*si;
156                     this.matrix[2][0] = y*(1-co)-x*si;
157                 }
158             };
159         } else if (type=='shear') {
160             this.evalParam = JXG.createEvalFunction(board,params,1);
161             this.update = function() {
162                 var beta = this.evalParam(0);
163                 this.matrix[1][1] = Math.tan(beta); 
164             };
165         } else if (type=='generic') {
166             this.evalParam = JXG.createEvalFunction(board,params,9);
167             this.update = function() {
168                 this.matrix[0][0] = this.evalParam(0); 
169                 this.matrix[0][1] = this.evalParam(1); 
170                 this.matrix[0][2] = this.evalParam(2); 
171                 this.matrix[1][0] = this.evalParam(3); 
172                 this.matrix[1][1] = this.evalParam(4); 
173                 this.matrix[1][2] = this.evalParam(5); 
174                 this.matrix[2][0] = this.evalParam(6); 
175                 this.matrix[2][1] = this.evalParam(7); 
176                 this.matrix[2][2] = this.evalParam(8); 
177             };
178         }
179     },
180 
181     /**
182      * Transform a GeometryElement:
183      * First, update the matrix
184      * Second, do the matrix-vector-multiplication
185      *
186      * @param {JXG.GeometryElement} element, which is transformed
187      */
188     apply: function(p){
189         this.update();
190         if (arguments[1] != null) {
191             return JXG.Math.matVecMult(this.matrix,p.initialCoords.usrCoords);
192         } else {
193             return JXG.Math.matVecMult(this.matrix,p.coords.usrCoords);
194         }
195     },
196 
197     /**
198      * Apply a transformation once to a GeometryElement.
199      * If it is a free point, then it can be dragged around later
200      * and will overwrite the transformed coordinates.
201      * @param {JXG.Point|Array} p
202      */
203     applyOnce: function(p){
204         var c, len, i;
205         
206         if (!JXG.isArray(p)) {
207             p = [p];
208         }
209         
210         len = p.length;
211         for (i = 0; i < len; i++) {
212             this.update();
213             c = JXG.Math.matVecMult(this.matrix, p[i].coords.usrCoords);
214             p[i].coords.setCoordinates(JXG.COORDS_BY_USER, c);
215         }
216         
217     },
218 
219     /**
220      * Bind a transformation to a GeometryElement
221      */
222     bindTo: function(p){
223         var i, len;
224         if (JXG.isArray(p)) {   
225             len = p.length;
226             for (i=0; i<len; i++) {
227                 p[i].transformations.push(this);
228             }
229         } else {
230             p.transformations.push(this);
231         }
232     },
233 
234     setProperty: function(term) {
235     },
236 
237     /**
238      * Multiplication of a transformation t from the right.
239      * this = t join this
240      */
241     melt: function(t){
242         var res = [], i, len, len0, k, s, j;
243         
244         len = t.matrix.length;
245         len0 = this.matrix[0].length;
246         
247         for (i=0;i<len;i++) {
248             res[i] = [];
249         }
250         this.update();
251         t.update();
252         for (i=0;i<len;i++) {
253             for (j=0;j<len0;j++) {
254                 s = 0;
255                 for (k=0;k<len;k++) {
256                     s += t.matrix[i][k]*this.matrix[k][j];
257                 }
258                 res[i][j] = s;
259             }
260         }
261         this.update = function() {
262             var len = this.matrix.length,
263                 len0 = this.matrix[0].length;
264             for (i=0;i<len;i++) {
265                 for (j=0;j<len0;j++) {
266                     this.matrix[i][j] = res[i][j];
267                 }
268             }
269         };
270         return this;
271     }
272 });
273 
274 JXG.createTransform = function(board, parents, attributes) {
275     return new JXG.Transformation(board, attributes['type'], parents);
276 };
277 
278 JXG.JSXGraph.registerElement('transform', JXG.createTransform);
279